Understanding Java Generics: Basics, Wildcards, Bounds, and Type Erasure
This article introduces Java generics, covering generic classes and methods, wildcard usage with the PECS principle, bounded type parameters, and the implications of type erasure, illustrating each concept with code examples and discussing common pitfalls such as generic arrays, bridge methods, and runtime type checks.
Java generics, introduced in JDK 1.5, provide compile‑time type checking that helps catch illegal type usage early; they are heavily used throughout the Java Collections Framework.
Generic class example : a non‑generic
public class Box { private String object; public void set(String object) { this.object = object; } public String get() { return object; } }can store only String values, while the generic version
public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } }can be instantiated as Box<Integer> integerBox = new Box<Integer>();, Box<Double> doubleBox = new Box<Double>();, Box<String> stringBox = new Box<String>();.
Generic method example : declaring a method with type parameters before the return type, e.g.
public class Util { public static <K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } }together with
public class Pair<K,V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }. Calls can use explicit type arguments boolean same = Util.<Integer,String>compare(p1,p2); or rely on type inference boolean same = Util.compare(p1,p2);.
Bounded type parameters : a naïve method
public static <T> int countGreaterThan(T[] anArray, T elem) { int count=0; for(T e:anArray) if(e > elem) ++count; return count; }fails because the > operator is not defined for arbitrary types. By constraining T to Comparable we can write
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) { int count=0; for(T e:anArray) if(e.compareTo(elem) > 0) ++count; return count; }.
Wildcards and the PECS principle : a method public void boxTest(Box<Number> n) { /* ... */ } cannot accept Box<Integer> or Box<Double> because generic types are invariant. Using wildcards, a covariant reader can be defined as
static class CovariantReader<T> { T readCovariant(List<? extends T> list) { return list.get(0); } }and used with
CovariantReader<Fruit> fruitReader = new CovariantReader<>();to read from List<Fruit> or List<Apple>. The PECS rule states “Producer extends, Consumer super”: List<? extends Fruit> flist = new ArrayList<Apple>(); allows reading but not adding (except null), while List<? super Fruit> flist = new ArrayList<Object>(); permits adding Fruit instances but not reading specific subtypes. The standard library method
public static <T> void copy(List<? super T> dest, List<? extends T> src) { for(int i=0;i<src.size();i++) dest.set(i, src.get(i)); }combines both forms.
Type erasure : generic type information is removed after compilation. A class
public class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } }is compiled to
public class Node { private Object data; private Node next; public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } }. Adding a bound, e.g. class Node<T extends Comparable<T>> { … }, changes the erased type to Comparable instead of Object.
Problems caused by erasure :
Generic arrays are prohibited:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile‑time errorbecause the runtime cannot distinguish the element type.
Bridge methods are generated to preserve polymorphism, as shown by
class MyNode extends Node<Integer> { public void setData(Integer data) { … } // bridge: public void setData(Object data) { setData((Integer) data); } }, which can lead to ClassCastException when a raw Node reference receives an incompatible value.
Instance creation with a type parameter is illegal ( E elem = new E();), but can be achieved via reflection:
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); list.add(elem); }.
The instanceof operator cannot be used with concrete generic types; using a wildcard makes it reifiable: if(list instanceof ArrayList<?>) { … }.
These examples demonstrate both the power and the limitations of Java generics, emphasizing the need to understand bounds, wildcards, and type erasure when designing type‑safe APIs.
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.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.
