Understanding Upper and Lower Bounds in Java Generics
This article explains Java generics' upper (extends) and lower (super) bounds, illustrating their usage with comprehensive code examples for generic classes, methods, and collections, and compares reading versus writing scenarios, helping developers write more flexible and type‑safe Java code.
1. Introduction
Java generics allow classes, interfaces, and methods to operate on objects of any type without specifying a concrete type. Upper and lower bounds can constrain type parameters to improve type safety and flexibility.
This article introduces the concepts of upper and lower bounds in Java generics with extensive example code.
2. Basic Concept of Generics
Generics enable definition of classes, interfaces, and methods with type parameters. Example of a generic class Box<T> that stores a value of type T.
public class Box<T> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
} Box<Integer>or Box<String> can be instantiated with specific types.
3. Upper Bound (extends)
Upper bound restricts a type parameter to a specific type or its subtypes using the extends keyword.
Example: List<? extends Number> can hold Number or any subclass such as Integer, Double.
3.1 Example: Upper bound in generic class
Class Box<T extends Number> restricts T to Number or its subclasses.
public class Box<T extends Number> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}Usage in Main demonstrates Box<Integer> and Box<Double> instances, while Box<String> is illegal.
public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);
System.out.println("Integer Box Value: " + integerBox.getValue());
Box<Double> doubleBox = new Box<>();
doubleBox.setValue(10.5);
System.out.println("Double Box Value: " + doubleBox.getValue());
// Box<String> stringBox = new Box<>(); // compile error
}
}3.2 Example: Upper bound in generic method
Method sum calculates the total of a list of numbers, with type parameter bounded by Number.
public class MathUtils {
public static <T extends Number> double sum(List<T> numbers) {
double total = 0.0;
for (T number : numbers) {
total += number.doubleValue();
}
return total;
}
}Demonstration in Main shows summing integers and doubles.
public class Main {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1,2,3,4,5);
List<Double> doubles = Arrays.asList(1.1,2.2,3.3,4.4,5.5);
System.out.println("Sum of integers: " + MathUtils.sum(integers));
System.out.println("Sum of doubles: " + MathUtils.sum(doubles));
}
} Sum of integers: 15.0
Sum of doubles: 16.54. Lower Bound (super)
Lower bound restricts a type parameter to a specific type or its supertypes using the super keyword.
Example: List<? super Integer> can hold Integer, Number, or Object.
4.1 Example: Lower bound in generic method
Method addIntegers adds integers to a list whose element type is a supertype of Integer.
public class CollectionUtils {
public static void addIntegers(List<? super Integer> list) {
list.add(10);
list.add(20);
list.add(30);
}
}Usage with List<Number> and List<Object> demonstrates successful addition.
public class Main {
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
CollectionUtils.addIntegers(numberList);
System.out.println("Number List: " + numberList);
List<Object> objectList = new ArrayList<>();
CollectionUtils.addIntegers(objectList);
System.out.println("Object List: " + objectList);
}
} Number List: [10, 20, 30]
Object List: [10, 20, 30]4.2 Real‑world scenario for lower bound
Lower bounds are useful for handling covariance and contravariance, such as adding Dog objects to a List<? super Dog>.
public class AnimalShelter {
public static void addAnimals(List<? super Dog> animals) {
animals.add(new Dog());
// animals.add(new Cat()); // compile error
}
}Demonstrated with a List<Animal> where Dog instances can be safely added.
5. Comparison of Upper and Lower Bounds
Upper bounds ( extends) are typically used for reading, ensuring retrieved elements are of the bound type or its subtypes.
Lower bounds ( super) are typically used for writing, allowing addition of elements of the bound type or its subtypes.
5.1 Combining upper and lower bounds
Method copy uses both wildcards to read from a source list and write to a destination list.
public class MixedUtils {
public static <T> void copy(List<? extends T> source, List<? super T> destination) {
for (T item : source) {
destination.add(item);
}
}
}Example shows copying a List<Integer> into a List<Number>.
public class Main {
public static void main(String[] args) {
List<Integer> source = Arrays.asList(1,2,3,4,5);
List<Number> destination = new ArrayList<>();
MixedUtils.copy(source, destination);
System.out.println("Destination List: " + destination);
}
} Destination List: [1, 2, 3, 4, 5]6. Conclusion
The article detailed upper and lower bounds in Java generics, demonstrating how extends and super constrain type parameters, improve type safety, and enable flexible code. Proper use of bounds solves many complex type conversion and collection manipulation problems, leading to more robust and maintainable Java applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
