Generics were introduced with Java 6 and quickly become indispensable tool of every Java programmer. In this blog post I gathered the most important facts about generics in Java. After reading this post you you should be able to comfortable use generics in your code.
We can declare generic
Pair class using syntax:
Then we can use
Pair as follows:
In Java 7 and later we can let compiler infer values of generic parameters
new expression by using diamond operator (
We can reduce possible values of generic parameters by using bounds.
For example to reduce values of parameter
T to types that
Serializable interface we can write:
When using bounds we are not limited to single type.
For example we may
require that types allowed for
T must extend
When we try to use
SerializableList with types that doesn’t
conform to our bounds we will get compile-time error:
In Java generics are implemented via type erasure, this means that
generics exists only in Java source code and not in JVM bytecode.
When Java compiler translates generic classes to bytecode it
substitutes generic parameters in class body with
or if generic parameter has bounds with value of the first bound.
For example our
Pair class would be translated by compiler into:
Compiler also inserts necessary casts, and converts between primitives
and wrappers (e.g. between
Integer) - a process
called (un)boxing. Continuing our example:
is translated by compiler into:
Generics were implemented via type erasure to preserve binary compatibility with pre Java 6 code (binary compatibility means that your old code will work with generic types out of the box - you don’t need to change or recompile your legacy libraries).
Shortcomings of type erasure
Type erasure is not the best way to implement generics, and IMHO Java should take a different approach (e.g. reification). But Java didn’t and we are stuck with “type erasure” generics. Below is a list of Java generics shortcomings:
- Primitive types like
intcannot be used with generics. We may use generics with wrapper types e.g.
Integerbut this will incur performance penalty caused by casts and boxing/unboxing operations.
- We cannot use generic parameters in declarations of static class members. Static class members are shared between all instances of generic class regardless of generic parameters values. To stress this fact Java allows to access static members only via class name without generic parameters:
- Sometimes Java compiler must create synthetic methods (in this case called bridge methods) to make overriding work with generic types. For example:
after type erasure becomes:
and we can see that
TestClass no longer overrides
consume method from
TestInterface. To solve this
problem compiler adds following method to
- Generics don’t work well with overloading, for example following overloads are forbidden:
because after type erasure both methods have exactly the same signature
Similarly we cannot implement the same interface twice with different generic parameters. Again type erasure is our culprit:
Raw types and unchecked warnings
To maintain backward compatibility Java allows us to use generic types without specifying generic parameters. Such types are called raw types, for example:
Raw types can be treated like generic types after type erasure. Raw types should only be used to interact with legacy code.
When working with raw types compiler may generate an unchecked warning:
This warning means that compiler is not sure if we used generic type
correctly and in case that we didn’t we should expect
at runtime. For example:
The problem here is that the client of
legacyCode expected that
will add integer to provided list. A simple solution is to use
List<Object> instead of
legacyCode may add different types to list.
Notice also that line which generated unchecked warning didn’t throw
any exception, exception was thrown later when the
client wanted to access list element.
We may suppress unchecked warning at the method or class level by using
We can declare generic method as follows:
Generic methods are invoked like ordinary methods:
In most cases compiler will be able to infer proper values of generic parameters. When it won’t we can override compiler by explicitly specifying generic parameters values:
Let’s consider method that copies elements from one list to another:
It works perfectly with lists of integers:
But fails when we want to copy integers to list of numbers:
We may fix method by adding second generic parameter:
This is so common situation that Java introduces a shortcut:
List<? extends T> means that this is a list of elements that
extends or implements type
Wildcards allows us to reduce number of required generic parameters and made method declarations more clear, for example:
Collections<?> means collection of elements of some certain type
e.g. this may be
super bound may be used only with wildcards.
super bound restricts values of wildcard to given class
and all of its superclasses, for example method:
can only be used with
Calling method with
List<String> results in compile time error.
extends bound is useful when we want to get values
from generic type instance,
super bound is needed when we want to pass values to generic type instance.
Producer may produce type
T or more derived type, and
may consume type
T or more general type e.g.
Object. Thanks to
wildcards we may use
NOTE: Java compiler tries to infer the most specific type for
generic parameters. In call to
Integer will be used as
T parameter value.
Let’s say that we want to create a method that swaps elements of the list, we may write:
Unfortunately above code doesn’t compile. We may either introduce generic parameter to method signature or create a helper method with generic parameter that will “capture” wildcard value:
Introducing generic parameter is always better solution than using wildcard capture. I only mention above technique because it is often used in Java Collection Framework.
Covariance and contravariance
With Java arrays we may write:
We say that Java arrays are covariant.
Generics in Java are invariant this means that below code doesn’t compile:
We must tread
List<Object> as two
Still we may use wildcards to refer to either
List<?> should be treated as superclass of any
because it represents list of objects of some certain type.
We can’t do much with
List<?>, we can only get
Objects from it,
nulls and ask for size (operations allowed for any list):
List<?> are limited because we don’t know what types list contains.
We may limit range of possible types with bounds thus gaining
Now compiler knows that list elements are at least numbers so
we may assign result of
get() to variable of type
Still we are not able to put anything beyond
null into list,
because we don’t know if this is a list of doubles or a list of integers.
When we want to add elements to list we should use
Now compiler knows that list holds numbers or elements more general than numbers e.g. objects, so adding number to list is safe.
How to use generic types with
Because of type erasure types
indistinguishable to JVM. To check if value is instance of
List<Integer> are represented
by the same class token:
Notice that in instance test we should use type with wildcard (
List<?>) but to
get class token we should use raw type (
Generics and arrays
Let’s consider this innocent looking code:
Calling this method results in
Because we cannot assign
Object instance to
The source of the trouble is type erasure again.
Because value of parameter
T is not accessible at runtime
we don’t know what array we should create - should it be array of
objects or maybe array of strings. We may fix this method by passing
additional parameter that will represent required type of array elements:
Now method works as expected but is cumbersome to use.
Another problem with arrays and generics is that we cannot create array with generic elements - type erasure is culprit again:
To create array of generic types we must use raw type and cast.
To sum up: you should avoid mixing arrays and generics.
Generics and varargs
Java varargs methods are implemented using arrays, when we try to use varargs with generics:
compiler issues a warning:
Generic varargs suffer from the same problems as generic arrays.
If we are sure that our code is safe,
we may use
@SafeVarargs annotation to suppress this warning.
If you want to know more about generics check resources below: