Generics have one thing in common with regular expressions: Students think they are an easy to use tool but they just end up with code they do not understand and can’t maintain. Regular expressions can often be replaced by well written and documented code. But you can’t just replace generics. A student using collections with generic type parameters but not fully understanding the concept is still better than one using “raw” collections. And it’s often ok to use something just to get familiar with the idea before learning the theory. The downside is that many misconceptions emerge from this approach of teaching. In this post I try to explain some of the common misconceptions on generics in Java.
Generics are not Templates
If you know templates from C++ then you will see that they are similar. But they are quite different. They are different solutions for the same problem. In C++ you basically write a code template and the compiler will replace some placeholder with values (types, numbers etc.) during compilation. In Java the compiler only checks for correct usage, but simply removes some of the generic type information (see “Type Erasure” below).
Generics are not just about Collections
The Java Collections Framework (JFC) uses generics. But not only data structures use generics. They are an “enhancement to the type system [that] allows a type or method to operate on objects of various types while providing compile-time type safety” (Tutorial by Gilad Bracha). Collections are only one kind of code that benefits from generics. Types such as
Future use generics too.
This is hard to learn and fully understand. I’ll try to give two very simple examples:
List<? extends Foo> list1;
list1 refers to a List of an unknown type of elements. The question mark is a wildcard. It’s not known what the list really holds. But whatever it is, it must extend
Foo. So if you extract elements you can treat them as Foo. But you can’t put anything into the list, because you wouldn’t know if it’s the correct type.
List<? super Foo> list2;
Now we have a second List, referenced by
list2. The actual type is not known, but it must be a supertype of
Foo. So if you have an instance of
Foo it’s safe to insert it into
list2. But when you get an item from the list you can’t know what it is.
A common misconception is that
List<?> are the same type. But this statement makes no sense. The wildcard says that the type is unknown. And it isn’t known if two unknowns are the same. So obviously you can’t insert elements form one list into the other. The Java compiler wouldn’t even allow to take an element of a
List<?>-typed list and insert into the very same list.
Bounds and Families
- In the context of generics a family is a set of types. However, the actual types (the elements of the set) are not known and not relevant. The actual size of family might depend on which types are available at runtime (which libraries are loaded).
- A bound defines a subset that excludes all types that are outside that bound.
Objectis the upper bound if none is given. No type is above
(This might change in a future release of Java.)
- All final classes have no subclasses, so it doesn’t make sense to use them as a lower bound.
<? extends String>is the same as just
<String>, because it defines a family that only contains
String. However, the compiler is not fully aware of this and therefore no elements could be added to a list of type
List<? extends String>. A singleton family is not equal to the type it contains.
- Array types can not be used as bounds.
- The bounds are always inclusive. The family
<? extends X>includes the type X.
- Producer: extends, consumer: super (PECS):
If a producer of
Tis needed then every producer of any type of the family
<? extends T>is ok.
If a consumer of
Tis needed then every consumer of any type of the family
<? super T>is ok.
- Parameter bounds can use the intersection of one class type bound and any number of interface type bounds:
<? extends ClassX & InterfaceY & InterfaceZ>
The class definition of
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
This is simply used so that a type can hold a method that takes or returns values on it’s own type. It’s the subtype of
compareTo doesn’t just take any
Enum, it only accepts an instance of the actual subtype (what is set as
E). This is used so that the compiler can make sure all subclasses define on what type the superclass works. Since Enums are a language construct you don’t need to do this, the compiler does.
Method Signature and Invocation
This is a bit confusing in Java. (It’s different in C#.) The generic type parameters come before the return type. So they are far away from the method parameters.
public <T> long myMethod(T t)
You can invoke this method like this:
Again, the generic type parameter is before the method name. However, in most cases you do not need to explicitly set the type, because the compiler can infer it from the given parameter value. And during compilation it’s removed.
As I understand it, some developers at Oracle are not happy that they decided to “erase” some of the generic type information during compilation. Maybe this changes in a future release of Java. But all this talk about erasure leads to the misconception that all generic types are simply removed or replaced by
Object. When you use
T extends Foo it will obviously replace this by
Object. So not everything is lost. And by reflection you can actually get all information on declared generic type parameters during runtime. Some frameworks use this.
To fully understand this you should read about reifiable types here:
JLS > 4. Types, Values, and Variables > 4.7. Reifiable Types
And then there are bridge methods:
The Java Tutorial > Effects of Type Erasure and Bridge Methods
Those are technical details, and it’s like that for backwards compatibility with earlier versions. It’s important to understand this to use reflection on generic types, as some frameworks do.
A complete Guide to Generics
You can learn the basics from your text book or this tutorial:
Oracle Tutorial: https://docs.oracle.com/javase/tutorial/java/generics/
And if you want to be an expert you can learn all the details here:
Java Generics FAQs: http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html