Java Generics Explained

Darren Wegdwood
4 min readJan 16, 2020

--

It is important to understand that Java generics is not true generics, it offers only syntactical support to take advantage of compile time safety check. All generics gets compiled to non-generic code during byte code generation.

Before Generics…

In the beginning, Java doesn’t support generics, and this is how you would manipulate a List:

List myList = new ArrayList();
myList.add(“filthy frank”):
String str = (String) myList.get(0); // "filthy frank"
Integer i = (Integer) myList.get(0); // error: ClassCastException

Lots of casting, not very type-safe huh, must be pretty common to get a ClassCastException™️.

When JCP introduced generics in Java 5, they have to make sure full backward compatibility with this kind of non-generic code. So, type erasure was introduced.

Type Erasure

When you write code that made use of generics

List<String> myList = new ArrayList<>();
myList.add("filthy frank");
String s = myList.get(0);

It gets compiled to

List myList = new ArrayList();
myList.add(“filthy frank”):
String s = (String) myList.get(0);

Notice how similar it is to pre-Java 5 code, because parameterised type <> information are only used during compile time for type check. After compilation, they are erased and replaced with raw type and casting, the generated byte code is almost the same as pre-Java 5 code.

You can think of Java generics as syntactic sugar over raw type and manual castings. After compilation, type erasure converts the sugar to non-generic compliant code.

With Type-Erased Generics, you get

  1. Compile-time safety check 👌
  2. Backward compatibility with pre-Java 5 code 👌
  3. No type information available at runtime 😢

Reification & Reified Types

Reification sounds like a term that software people invented to protect their job. This is the definition of Reify from Merriam Webster,

To consider or represent (something abstract) as a material or concrete thing

Reification is the opposite of Type Erasure, it is the process of making type information available at runtime.

Java supports reification for most of the types, like primitives (int, char) , non-parameterised type (Number, InputStream), array of primitives (int[]), etc.

These are called Reifiable Types, they retain their type information at runtime, because they are not subjected to type erasure.

The following 2 types are NOT Reifiable Type:

  1. Exact parameterised type — List<Number>
  2. Bounded parameterised type — List<? Extends Number>

They lose their type information at runtime due to type erasure. As such, Java has limited support for reification, and is not consider a Reified Generics system.

Reifiable types in action

String[] strArray = { "a", "b };
Object[] objArray = strArray;
System.out.println(objArray instanceof String[]); // true, objArray is reified to String[]
Number number = 1;
System.out.println(number.getClass()); // java.lang.Integer, number is reified to Integer

Non-Reifiable types in action

```
List<String> list = new ArrayList<>();
System.out.println(list.getClass()); // java.util.ArrayList, list is not reified to ArrayList<String>
```

Reified Generics vs Type-Erased Generics

Reified Generics is a generics system that makes generics type information available at runtime.

C# is one language that supports Reified Generics natively, as opposed to Java’s Type-Erased Generics.

There are, however, some obscure dark magic that allows you to make reified generics happen in Java using Type Token, but that belongs in the Restricted Section.

Let’s take a look at what is possible if java were to support Reified Generics.

Overload method with generics

public void process(List<String> l) {
System.out.println("Processing list of string");
}
public void process(List<Integer> l) {
System.out.println("Processing list of integer");
}

Get type at runtime

public <T> Class<T> getReifiedType(List<T> object) {
return object.getClass(); // return List<String>.class
}
public <T> boolean isListOfString(List<T> object) {
return object instanceof List<String>.class; // return true
}

Generic Array

T[] genericArray = new T[];

Raw Type

Sometimes we get a warning about using raw type in the IDE

List list = new ArrayList<Integer>(); // warning: Raw use of parameterized class 'List'

Raw type is basically the use of generics without specifying type parameter <>

It was ubiquitous before Java 5, but with the introduction of generics, the added benefit of compile time checking is completely subverted by the use of raw type, defeating the purpose of generics.

Notice how this is merely a warning instead of error, because raw type is known to be potentially dangerous, nevertheless, it is allowed for backward compatibility.

Usage of raw type could lead to Heap Pollution, which in turn produces ClassCastException™️. There is even a rule from Effective Java — Item 26: Don’t use raw types.

How does Raw Type causes Heap Pollution?

Another simple concept with a complicated name.

Heap pollution happens when a variable of type X doesn’t reference X or its subclass, but references Y

How is this even possible with a strongly typed language? Well, because Java allows you to use Raw Type!

Consider the following code:

List intList = new ArrayList<Integer>(); // raw type warning 
List<String> strList = intList; // no warning
intList.add(new Integer(3));
String x = strList.get(0); // error: ClassCastException

In line 2, strList is referencing a list of integer (intList), but compiler doesn’t complain because it doesn’t know that intList is a list of Integer due to the use of raw type in line 1.

When you try to get a string out of strList, you will get ClassCastException™️. In a way, you are “polluting” the heap by putting a type where it shouldn’t be.

--

--