Fundamentals 17 min read

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 Captain
Java Captain
Java Captain
Understanding Java Generics: Basics, Wildcards, Bounds, and Type Erasure

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 { private T t; public void set(T t) { this.t = t; } public T get() { return t; } } can be instantiated as Box integerBox = new Box (); , Box doubleBox = new Box (); , Box stringBox = new Box (); .

Generic method example : declaring a method with type parameters before the return type, e.g. public class Util { public static boolean compare(Pair p1, Pair p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } together with public class Pair { 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. compare(p1,p2); or rely on type inference boolean same = Util.compare(p1,p2); .

Bounded type parameters : a naïve method public static 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 > 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 n) { /* ... */ } cannot accept Box or Box because generic types are invariant. Using wildcards, a covariant reader can be defined as static class CovariantReader { T readCovariant(List list) { return list.get(0); } } and used with CovariantReader fruitReader = new CovariantReader<>(); to read from List or List . The PECS rule states “Producer extends, Consumer super”: List flist = new ArrayList (); allows reading but not adding (except null ), while List flist = new ArrayList (); permits adding Fruit instances but not reading specific subtypes. The standard library method public static void copy(List dest, List src) { for(int i=0;i combines both forms.

Type erasure : generic type information is removed after compilation. A class public class Node { private T data; private Node next; public Node(T data, Node 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 > { … } , changes the erased type to Comparable instead of Object .

Problems caused by erasure :

Generic arrays are prohibited: List [] arrayOfLists = new List [2]; // compile‑time error because the runtime cannot distinguish the element type.

Bridge methods are generated to preserve polymorphism, as shown by class MyNode extends Node { 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 void append(List list, Class 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.

JavaReflectionGenericsPECSType Erasurewildcards
Java Captain
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.