Generic type arguments constrain source-level use of a type; erasure does not turn unrelated generic declarations into interchangeable compiler types.
Java generics interview: variance, erasure and compiler boundaries
Java 8+. This is a complete program using JDK classes.
Can List<Integer> replace List<Number>?
No. If that assignment were accepted, a consumer could insert a Double into storage another caller expects to contain only Integer. List<? extends Number> instead supplies a read-oriented number boundary, while List<? super Integer> permits inserting Integer values without promising that reads return Integer.
The copy method has an integer-producing source and an integer-accepting destination. It can copy to a List<Object> because that destination can hold integers. It does not cast the source to a raw list or claim every object already in the destination is an integer.
What survives erasure?
The runtime class of ArrayList<Integer> and ArrayList<String> is the same implementation class. That observation does not disable the compiler rules for their contents. Casts inserted at use sites can fail if raw or unchecked code polluted the structure earlier.
A generic array allocation such as new T[length] is unavailable because the runtime array element checks would need an element type that is not generally available after erasure. Supplying a checked array factory or keeping list storage is a clearer boundary than promising an unchecked cast is safe.
What does PECS leave out?
Producer-extends and consumer-super describe a direction for an API argument; they do not determine ownership, mutability or thread safety. A readable wildcard can still hold a mutable object, and a caller can still modify the underlying list through another alias.
Working program
import java.util.*;
public class IntegerBatchVariance {
static void copy(List<? super Integer> target,List<? extends Integer> source){
for(Integer amount:source)target.add(amount);
}
public static void main(String[] args){
List<Integer> quantities=new ArrayList<>(Arrays.asList(7,9));
List<? extends Number> observed=quantities;
List<Object> exported=new ArrayList<>();copy(exported,quantities);
System.out.println(exported);
System.out.println(observed.get(0).intValue());
System.out.println(quantities.getClass()==new ArrayList<String>().getClass());
}
}Output
[7, 9]
7
trueCosts and boundaries
Copying n entries visits O(n) references and grows destination membership storage. Variance does not copy values or synchronize the source traversal. A concurrently edited ordinary source needs an ownership or locking rule before this method can provide a consistent batch.
Common Mistakes
- List<Integer> is not a subtype of List<Number>.
- A readable wildcard does not freeze element state.
- An unchecked cast relocates a potential failure; it does not prove compatibility.
