In Java, generic collections are invariant. This means that even though Integer is a subclass of Number, an ArrayList<Integer> is not a subclass of ArrayList<Number>. To allow flexible relationships in collections, Java introduces **wildcards** (?) and **bounds**.
In this guide, we will explain upper bounds, lower bounds, and unbounded wildcards using a simple class hierarchy containing Person, Student, and Teacher.
Imagine a school campus containing People, Students, and Teachers:
- Upper Bound (
? extends Person): A door sensor says: "Only People and their subclasses (Students and Teachers) are allowed inside." A dog or a robot cannot enter. - Lower Bound (
? super Teacher): An admin committee rules: "Only Teachers and their superclasses (all People) are allowed to vote." Students are excluded. - Unbounded Wildcard (
?): A gatekeeper says: "I don't care who you are (Person, Student, or even an Object), I just want to count how many entities pass through."
The Class Hierarchy
For our examples, we will use this basic inheritance tree: Student and Teacher extend Person. There is also an unrelated class named Sardar:
class Person { String name; }
class Student extends Person {}
class Teacher extends Person {}
class Sardar {} // Unrelated class
1. Upper Bounded Wildcards (? extends T)
An upper bound restricts elements to a specific class or any of its subclasses. This is a read-only list representation:
static int count = 0;
static void countitem(ArrayList<? extends Person> l) {
// Only accept lists of Person, Student, or Teacher
for (Object o : l) {
count++;
}
System.out.println("Items added : " + count);
count = 0;
}
If we try to invoke this method with ArrayList<Sardar>, the compilation fails because Sardar is not a subclass of Person.
2. Lower Bounded Wildcards (? super T)
A lower bound restricts elements to a specific class or any of its superclasses (climbing up the inheritance tree). This is useful for writing to collections:
static void countitem(ArrayList<? super Teacher> l) {
// Accept lists of Teacher, Person, or Object
for (Object o : l) {
count++;
}
}
public static void main(String args[]) {
ArrayList<Person> ap = new ArrayList<>();
ArrayList<Student> as = new ArrayList<>();
ArrayList<Teacher> at = new ArrayList<>();
countitem(ap); // Works! Person is superclass of Teacher
countitem(at); // Works! Teacher matches Teacher
// countitem(as); // Fails! Student is not superclass of Teacher
}
3. Unbounded Wildcards (?)
If you don't care about the type parameter and want to perform operations that only depend on the collection structure (like checking size), use the raw wildcard ?:
static void addnum(ArrayList<?> l) { // Unbounded wildcard
System.out.println("List size: " + l.size());
}
This method accepts an ArrayList containing any object type (String, Integer, Student, etc.) without throwing errors.
Conclusion
Wildcards allow you to write reusable methods that accept subclasses or superclasses. Remember the **PECS** guideline:
- Producer Extends: Use
? extends Tif you only read from the collection. - Consumer Super: Use
? super Tif you only write to the collection.