Java Wildcard Super: Using ? super T Correctly
java wildcard super: Understand the `? super T` wildcard in Java generics, its role in the PECS rule, and how to use it for flexible, type-safe collection methods.
java wildcard super requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When working with Java generics, the super wildcard (? super T) often confuses developers because it appears to invert the direction of type compatibility. In practice, ? super T is a lower-bounded wildcard that lets you write methods accepting a broader set of types while preserving type safety. This article explains what ? super T means, how it differs from ? extends T, and where the PECS rule guides your choice.
The Problem ? super T Solves
Consider a method that adds an element to a collection. If you declare the parameter as Collection<Integer>, the method only accepts a collection of Integer. But what if you want to add an Integer to a Collection<Number> or Collection<Object>? Those are valid operations because Integer is a subtype of Number and Object. The method should accept any collection that can hold Integer values.
A naive attempt might use Collection<? extends Integer>. That would accept Collection<Integer>, but also Collection<? extends Integer> which could be a collection of some subtype of Integer (though Integer is final, so no subtypes exist). More importantly, ? extends Integer is an upper-bounded wildcard, and you cannot add to a collection declared with ? extends because the actual type parameter is unknown. The compiler prevents all add calls except null.
The super wildcard solves this: Collection<? super Integer> accepts Collection<Integer>, Collection<Number>, and Collection<Object>. It also allows you to add an Integer because you know the collection can hold at least Integer and any supertype.
Syntax and Meaning of ? super T
The syntax ? super T is a lower-bounded wildcard. It means the type argument is an unknown type that is a supertype of T (or T itself). In other words, the wildcard is bounded below by T. For example, List<? super Number> can be a List<Number>, List<Object>, or List<Serializable> (since Number implements Serializable, so Serializable is a supertype of Number). But List<Integer> is not allowed because Integer is a subtype of Number, not a supertype.
The key insight is that you can safely write (add) elements of type T (or any subtype of T) to a collection declared with ? super T, because the collection's actual type is at least T. However, when you read from such a collection, you can only assign the result to Object, because the actual type could be any supertype.
How ? super T Differs from ? extends T
The two wildcards are complementary:
? extends Tis an upper-bounded wildcard. It acceptsTor any subtype ofT. You can read from it asT, but you cannot write (exceptnull).? super Tis a lower-bounded wildcard. It acceptsTor any supertype ofT. You can write elements of typeT, but you can only read asObject.
This asymmetry is the foundation of the PECS rule.
The PECS Rule: When to Use super
PECS stands for "Producer Extends, Consumer Super." It is a mnemonic for choosing the right wildcard:
- If a method produces values of type
T(i.e., it reads from a collection and returnsT), use? extends T. - If a method consumes values of type
T(i.e., it writesTinto a collection), use? super T. - If a method both produces and consumes, use the exact type
Twithout a wildcard.
For example, the Collections.copy method uses both: public static <T> void copy(List<? super T> dest, List<? extends T> src). The destination consumes T, so it uses ? super T; the source produces T, so it uses ? extends T.
Practical Example: Adding to a Collection
Here is a method that adds a list of integers to any collection that can accept them:
public static void addIntegers(Collection<? super Integer> dest) { dest.add(1); dest.add(2); dest.add(3); }
You can call this method with Collection<Integer>, Collection<Number>, or Collection<Object>. The compiler allows the add calls because Integer is a subtype of the wildcard's lower bound. If you tried to call addIntegers with a Collection<String>, the compiler rejects it because String is not a supertype of Integer.
Now consider a method that reads from such a collection:
public static void printAll(Collection<? super Integer> col) { for (Object obj : col) { System.out.println(obj); } }
Because the actual type parameter is unknown, the only type you can safely assign each element to is Object. You cannot assign to Integer or Number without an explicit cast, which might fail at runtime if the collection actually holds Object values.
Common Mistakes and Type Inference Issues
A frequent mistake is using ? super T when ? extends T is needed, or vice versa. Another is assuming you can read a specific type from a ? super T collection. For example:
List<? super Integer> list = new ArrayList<Number>(); Integer i = list.get(0); // Compile error: incompatible types
The get method returns Object because the wildcard's upper bound is Object. You need to cast, but the cast may fail if the list contains a Double.
Type inference can also be tricky. When you call a generic method with a ? super T parameter, the compiler may infer T from the arguments. For instance, Collections.copy infers T from both the destination and source. If the types don't align, you may need to specify the type witness explicitly.
Runtime Behavior and Type Erasure
At runtime, Java erases generic type parameters. The wildcard ? super T is erased to its leftmost bound, which is Object for a lower-bounded wildcard. This means there is no runtime distinction between List<? super Integer> and List<Object>; both are just List. The compiler enforces type safety at compile time, and the runtime performs no additional checks for the wildcard itself.
This erasure has practical implications. You cannot use instanceof with parameterized types, and you cannot create arrays of parameterized types. The wildcard is purely a compile-time construct.
Maintainability and API Design Considerations
Choosing the right wildcard affects how flexible and maintainable your API is. Using ? super T for consumer parameters makes your methods more reusable without sacrificing type safety. For example, a method that adds elements to a collection is more useful if it accepts Collection<? super Integer> rather than Collection<Integer>. This allows callers to pass a Collection<Number> or Collection<Object> when appropriate.
However, overusing wildcards can make method signatures harder to read. If a method only needs to add elements and never reads them, ? super T is appropriate. If it both reads and writes, you should use the exact type T to avoid confusing callers. The PECS rule provides a clear guideline: producers extend, consumers super.
When designing a public API, consider whether the wildcard is part of the contract. A method signature with ? super T tells callers that the method will add elements but may not preserve the exact type. This is a useful semantic signal.