Java Generics

Part of my job as a Java developer I need to navigate some areas of the code base that I’m not as familiar with. Generics is one of the more arcane areas of Java. I decided to go head first into documenting my journey in learning the application of generics.

Introduction

Generics are a Java feature available since Java 1.5

It allows for passing type information to allow for better code reuse.

Classic example to start things off easy:

ArrayList<T> mandates that the class may use elements of some particular type T. Whether that be String, Integer, Address (for example) etc.

The usefulness comes in that if we try to add an object of an incorrect type, the code will not compile. Generics add compile time safety

 

T is the placeholder for the type information. It may be useful to circumvent this convention and make the typeholder name more explicit, for instance TRecord.

? indicates some unknown but specific type. Java type inference will work out the ? (known as a capture) by trying to bind this with the type of some known value.

It may try to work this out from a parameter passed in for instance.

 

Simple example for a class

public class MyClass<T extends Number>

 

Simple example at method level. Note that the generic type should appear BEFORE the return type:

 

public static <T extends Recordable> void asyncProcessQuery(...) {
...
}

 

Ranges

super / extends specify acceptable types in context of a particular hierarchy

For instance if we have the hierarchy (with some made up types for ease of illustration):

Object → Number → Integer → AwesomeInteger → SuperAwesomeInteger

“super AwesomeInteger” is a lower bound: It contains the types: Object, Number, Integer, AwesomeInteger (it contains itself)

“extends Integer” is an upper bound: It contains the types Integer (always includes itself), AwesomeInteger, SuperAwesomeInteger

 

Lets take an example using List<? extends Integer> someList = new ArrayList<>();

To some (and it included me at one stage), this appears to say that an ArrayList can contain any combination of elements, as long as it is Integer or beyond. THIS IS WRONG!

You cannot add an Integer to this list, nor can you add a SuperAwesomeInteger. For reasons that will be explained. You cannot add anything to it!

What this is saying, is that I can assign a List that is of a particular known type that starts from Integer or a subclass of

List<? extends Integer> someList = new ArrayList<Integer>();

List<? extends Integer> anotherList = new ArrayList<AwesomeInteger>();

 

Contrarily with super, the semantics are reversed

 

List<? super Integer> someList = new ArrayList<Number>();

is acceptable.

PECS

This is a mnemonic devised by Josh Block (the author of Generics)

To boil it down simply

if you want to SET something, you use super

If you want to READ something, you use extends

If you want to both SET and READ, then use the exact type.

 

For instance, if we have List<? super Integer> list = new ArrayList<>();

What values can we add to it?

Tempting to think that we can add Integers, Number, and Objects, as that’s the types defined within the super clause. No we can’t do that.

We can add Integer, AwesomeInteger and any further subclass.

The reason being, we cannot know which type this List will be. Hence we must use an object that would satisfy any of them.

If we plug in a value of SuperAwesomeInteger, AwesomeInteger or Integer, then this will be fine, as each of these types can be assigned to a list of List<Object>, List<Number>, List<Integer>

So, again if we want to write/add something, we use super

 

On the flip side when we use “extends” we cannot write to it but we can read from it.

For instance List<? extends Integer>

We can assign a list of List<Integer>, List<AwesomeInteger> etc. to the above list.

But given the List<? extends Integer> declaration, we cannot know which List will be assigned. Only that some list that is a subclass of Integer will be, e.g. we could assign List<SuperAwesomeInteger> but not List<Number> (as it appears below our bound)

 

With this in mind, what we know is that whatever the List is, we can READ from it. However we can’t necessarily read it as a SuperAwesomeInteger, as the list might actually be pointing to Integer (which won’t have the attributes of a SuperAwesomeInteger)

What we do know is that we can MINIMALLY read any of these objects as Integer (or Number or Object)

 

for (Integer i : myList) {

system.out.println(i.sigNum()) // some method on Integer

}

 

A note: “extends” in the context of Generic type constraints can either mean extends in the case of classes, but should be read as ‘implements’ when an interface is present, as we aren’t really extending an interface but checking that some type implements an interface.

The rule for using ampersand here is that the first type can be concrete or an interface. Any further types (i.e. after the &) must be interfaces.

This table shows where you can validly use extends / super

 

T extends Something ? extends / super Something
Used in class as a declaration

Ex:

Class List<T>, class List<T extends Number>

First style is more common in Java code, it is implicitly T extends Object

Usage of class, ex

List x = new ArrayList<? Extends Number>

Specifies unknown but specific type

Used in LHS (i.e. before the return type) of method as a declaration of the method.

Ex:

static <T extends Number> void gPrintA(List<T> l)

Method level. Usage

Ex:

void gPrint(List<? extends Number> l)

Cannot be used when invoking class.

List a = new ArrayList<T extends Number>

(Can only use ? extends/super)

Cannot be used in class definition

Class Brother<? Extends Number>

 

Captures

 

? have to be “captured” i.e. the compiler has to know what each ? is bound to. It can be a T type.

 

Example helper

 

public static void reverse(List<?> list) { rev(list); }

private static <T> void rev(List<T> list) {

List<T> tmp = new ArrayList<T>(list);

for (int i = 0; i < list.size(); i++) {

list.set(i, tmp.get(list.size() – i – 1));

}

}

 

Further reading

super and extends https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java

Java ? capture https://docs.oracle.com/javase/tutorial/java/generics/capture.html

Curiously recursive template pattern (used in builders) https://nuah.livejournal.com/328187.html

PECS https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/

Leave a Reply

Your email address will not be published. Required fields are marked *