Bounded Types and Wildcards (? extends T, ? super T)
Bounded types and wildcards in Java generics are powerful concepts
that enable more flexible and reusable code. They provide
fine-grained control over type parameters in generic classes,
methods, and interfaces. In this article, we will explore the
concepts of bounded types and wildcards (? extends T,
? super T) and provide examples to demonstrate their
usage in advanced Java.
Step-by-Step Guide
Step 1: Understanding Bounded Type Parameters
In Java, you can restrict the types that can be used as type
parameters. This restriction is called a
bounded type parameter. Bounded types are specified using the
extends keyword for upper bounds, meaning the type
parameter must be a subclass of a given type.
Example: Bounded type parameter with an upper bound
// Bounded type parameter example
class Box<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class BoundedTypeExample {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);
System.out.println("Integer Value: " + integerBox.getValue());
Box<Double> doubleBox = new Box<>();
doubleBox.setValue(10.5);
System.out.println("Double Value: " + doubleBox.getValue());
// The following will cause a compile-time error
// Box<String> stringBox = new Box<>(); // Error: String is not a subclass of Number
}
}
Step 2: Using Wildcards with Generics
Wildcards in Java generics are used to represent an unknown type.
The most common wildcard types are ? extends T (upper
bounded wildcard) and ? super T (lower bounded
wildcard). These wildcards allow you to work with generics without
knowing the exact type.
Step 3: Using ? extends T (Upper Bounded Wildcard)
The ? extends T wildcard is used when you want to allow
a method or class to accept types that are either of type
T or a subtype of T. This is useful when
you want to read data from a structure without modifying it.
Example: Using ? extends T to read elements of a
list
import java.util.*;
public class UpperBoundedWildcardExample {
// Method that accepts a list of elements that are instances of Number or its subclasses
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3, 4);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList); // Valid: Integer is a subclass of Number
printNumbers(doubleList); // Valid: Double is a subclass of Number
}
}
Step 4: Using ? super T (Lower Bounded Wildcard)
The ? super T wildcard is used when you want to allow a
method or class to accept types that are either of type
T or a supertype of T. This is useful when
you want to add elements to a structure but don't know the exact
type, as it guarantees that the structure can accept
T or any superclass.
Example: Using ? super T to add elements to a
list
import java.util.*;
public class LowerBoundedWildcardExample {
// Method that accepts a list of elements that are instances of Number or its superclasses
public static void addNumbers(List<? super Integer> list) {
list.add(10); // Valid: Integer is a subclass of Number
list.add(20); // Valid: Integer is a subclass of Number
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // Valid: Number is a superclass of Integer
List<Object> objectList = new ArrayList<>();
addNumbers(objectList); // Valid: Object is a superclass of Integer
// The following will cause a compile-time error
// List<Double> doubleList = new ArrayList<>();
// addNumbers(doubleList); // Error: Double is not a superclass of Integer
}
}
Step 5: Combining Bounded Wildcards
You can also combine ? extends T and
? super T in more complex scenarios. For example, you
might have a method that reads from one list (using
? extends T) and adds to another (using
? super T).
Example: Combining ? extends T and
? super T
import java.util.*;
public class CombinedWildcardExample {
// Method to copy elements from one list to another
public static void copyList(List<? extends Number> source, List<? super Number> destination) {
for (Number num : source) {
destination.add(num); // Add elements to the destination list
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Number> numberList = new ArrayList<>();
copyList(intList, numberList); // Valid: Integer is a subclass of Number
for (Number num : numberList) {
System.out.println(num); // Output: 1, 2, 3
}
}
}
Step 6: Best Practices for Using Wildcards and Bounded Types
-
Use
? extends Twhen you need to read from a structure, but don't want to modify it. -
Use
? super Twhen you need to add elements to a structure, but don't want to restrict the supertype. - Always ensure the wildcard type is compatible with the expected operations (reading or writing) on the data.
- Be cautious when combining multiple wildcard types to ensure the code remains readable and maintainable.
Conclusion
Bounded types and wildcards (? extends T,
? super T) in Java generics provide a way to write
flexible, reusable, and type-safe code. By understanding and using
these concepts, you can write methods and classes that work with a
wide variety of types while ensuring type safety. Wildcards and
bounded types are crucial for writing generic code that is both
efficient and maintainable.