4. Generic History 101
• Generics represent the most significant change
to the Java language since the 1.0 release.
• Over five years in the making, JSR 14 (Generics)
was one of the first Java Specification Requests.
• Generics are desirable because they let you write
code that is safer and easier to read than code
that is littered with Object variables and casts.
• Generic programming is now achieved via type
parameters as opposed to inheritance.
4
5. Life Before Generics
• Generalized classes, interfaces, and methods
were accomplished by operating through
references of type Object.
public Object get(int index);
• Explicit casting was required in order to downcast
Object to whatever concrete implementation
was being represented.
String value = (String) list.get(0);
• Runtime exceptions ran rampant because type
safety could not be guaranteed by the compiler.
5
6. The Generic New World Order
• Generics means parameterized types.
– The type upon which a generic method, class, or
interface operates is specified as a parameter.
• Generics make it possible to create a single
class, for example, that automatically works with
different types of data (Strings, Integers...).
• Type safety is now ensured by the compiler.
– All casts are automatic and implicit.
• Generics expand the ability to write reusable
code and do so in a safe, easy, and inviting
manner.
6
7. Raw Type vs. Generics Example
• Raw type version
List list = new ArrayList();
list.add("1");
String value = (String) list.get(0);
• Generics version
List<String> list = new ArrayList<String>();
list.add("1");
String value = list.get(0);
Note: The term “raw” refers to a generic type used without
actual type arguments. List is the raw type of List<E>.
7
8. Example Continued...
• Raw type version
List list = new ArrayList();
list.add("1");
// Compiler permits
list.add(new Integer(1));
// ClassCastException
String value = (String) list.get(1);
• Generics version
List<String> list = new ArrayList<String>();
list.add("1");
// Compilation error
list.add(new Integer(1));
String value = list.get(1);
8
10. The Collections Framework
• The entire collections framework has been
reworked using generics.
• Raw types provide a means of backward
compatibility (syntactic sugar).
– Under the hood, when a raw type is used, the compiler
actually substitutes the upper bound of each variable
(typically Object) as the actual type argument for that
variable. The following are equivalent…
List list = new ArrayList(); // Raw Type
List<Object> list = new ArrayList<Object>();
10
11. The New & Improved List Interface
public interface List<E> extends Collection<E> {
boolean add(E o);
E get(int index);
Iterator<E> iterator();
}
• E is a type variable or placeholder. It will be
replaced by the actual reference type passed as
an argument to the generic class.
11
12. List Interface Continued...
public interface List<String> extends Collection<String> {
boolean add(String o);
String get(int index);
Iterator<String> iterator();
}
• The type argument, String, replaces all
occurrences of E at runtime.
// String is passed as the parameterized type
List<String> list = new ArrayList<String>();
12
14. Generic Points of Interest
• The compiler generates only one class.
– All invocations share the same generic type.
• Only object reference types can be passed as
type parameters.
– Primitive types cannot be passed as type parameters.
• Generic type parameters are just like ordinary
method parameters. When invoked, the passed
type arguments (String) replace the actual type
parameters (E).
14
15. Points of Interest Continued...
• A reference of one specific version of a generic
type cannot be assigned to a different version of
the same same generic type.
// Wrong! (Same generic, different versions)
listOfStrings = listOfIntegers;
// Wrong! (This one’s tricky. Even though String
// subclasses Object, we can’t be sure listOfObjects
// is only holding Strings)
listOfObjects = listOfStrings;
• Generics are only implemented in the compiler.
– No generic type information is available at runtime due
to erasure.
15
17. The Concept of Erasure
• To ensure backward compatibility, a process
called type erasure is used to map the new
syntax to the current JVM specification.
• Type erasure is the process of translating or
rewriting code that uses generics into non-
generic code.
• When generics are compiled, all generic-specific
information is completely erased.
17
18. Erasure Continued...
• During erasure, all type variables are replaced by
their upper bound or Object if not specified.
// The erasure of T is Number
<T extends Number>
// The erasure of T is Object
<T>
• The compiler adds casts, type conversions, and
synthetic bridge methods as necessary to ensure
type-correct code.
– Bridge methods are inserted into subtypes of
parameterized supertypes to ensure that subtyping
works as expected.
18
19. Erasure Example...
// Pre-Erasure (Notice the type variables)
public void method() {
List<String> list = new ArrayList<String>();
list.add("value");
print(list);
}
// Pre-Erasure (Notice the type variables and omitted cast)
public void print(Collection<String> collection) {
Iterator<String> it = collection.iterator();
while (it.hasNext()) {
String element = it.next();
System.out.println(element);
}
}
19
20. Example Continued...
// Post-Erasure (Erased generics)
public void method() {
List list = new ArrayList();
list.add("value");
print(list);
}
// Post-Erasure (Erased generics and inserted cast)
public void print(Collection collection) {
String s;
for (Iterator it = collection.iterator(); it.hasNext();) {
s = (String) iterator.next();
System.out.println(s);
}
}
20
22. Multiple Type Parameters
• A generic type can accept more than one type
variable. For example, the Map interface accepts
two variables. The first (K) defines the key type
and the second (V) defines the value type.
public interface Map<K,V> {
V put(K key, V value);
V get(Object key);
}
Map<String, String> map = new HashMap<String, String>();
map.put("key", "value");
String value = map.get("key");
22
23. Passing Generics To Generics
• A generic type is itself a type that can be passed
to another generic. Below is how you would
create a list that holds a list of Strings.
List<String> listOfStrings = new ArrayList<String>();
listOfStrings.add("value");
List<List<String>> listOfLists = new ArrayList<List<String>>();
listOfLists.add(listOfStrings);
String value = listOfLists.get(0).get(0);
23
24. Wildcard Arguments
• Wildcards are used to signify an unknown type.
• Syntactically, wildcards are specified with <?>.
// List<?> is the pseudo-supertype of all lists
// It contains a list of unknown types
public void printList(List<?> list) {
// Type variables are always Objects
for (Object element : list) {
System.out.println(element);
}
}
Question: What is the difference between List<?> and List<Object>?
What would happen if we replaced the wildcard with Object?
24
25. Wildcards Continued...
• Wildcard parameterized types are similar to
interfaces...
– They can be declared, but no objects of a wildcard
parameterized type can be created.
// Invalid instantiation attempt of a wildcard
List<?> list = new ArrayList<?>();
– They can refer to an Object that is of a type that
belongs to a family of types the wildcard denotes.
// Valid because Long extends Number (same family)
List<? extends Number> list = new ArrayList<Long>();
// Wrong, String does not extend Number (not related)
List<? extends Number> list = new ArrayList<String>;
25
27. Bounded Types
• Used to restrict what can be passed to a generic
by defining an upper or lower bound of a type
variable.
• Both upper and lower bounds are inclusive.
• Bounds can be either a class or interface.
// The upper bound is an interface
List<? extends Serializable>
// The lower bound is a class
List<? super Integer>
27
28. Upper Bounds
• Upper bounds are defined as follows…
<? extends superclass>
• The extends keyword was chosen because it is
a reasonable approximation of the subtype
concept, and the Java designers didn’t want to
add a new keyword to the language.
• The following accepts a Shape or any subclass
of Shape such as Circle or Square.
// Shape is the upper bound
List<? extends Shape>
28
29. Lower Bounds
• While not as useful, it is also possible to define
the lower bound of a type variable, by using the
super keyword.
<? super subclass>
• The following will take a list of JDialogs or any
object whose class is a superclass of JDialog
such as Dialog or Window.
// JDialog is the lower bound
List<? super JDialog>
29
30. Multiple Bounds
• Type variables can also have multiple bounds.
• Use ampersands to separate bounded types.
<expression... class & interface & interface...>
• As with Java inheritance, there can be multiple
interfaces, but only one class, and it must be the
first bound specified in the list of bounded types.
// Number is the only class and is listed first.
// The interfaces are randomly ordered.
// Notice that T is passed to Comparable<T>.
<T extends Number & Comparable<T> & Serializable>
30
32. Generic Methods
• Methods with type parameters are referred to as
generic methods.
• They can exist within both generic classes and
non-generic classes.
• The scope of a method’s type parameter is
restricted to the method itself.
• Use when dependencies exist among the types
of one or more arguments and/or return type.
// The argument and return type are both of type T
public <T> T[] toArray(T[] a)
32
33. Generic Methods Continued...
• Insert type variables between the modifiers and
return type.
modifiers <typeVariables> returnType methodName(...)
• When calling a generic method, place the type
arguments before the method name.
objectReference.<typeVariables>methodName();
Note: In most cases, you can omit the type parameters from
the method call because the compiler will typically be able
to discern which method based on the actual type
arguments.
33
34. Generic Methods Continued...
public class Collections {
public static <T> void copy(
List<? super T> dest, List<? extends T> src) {
...
}
}
• T is a type variable or placeholder. It will be
replaced by the actual reference type passed as
an argument to the generic method.
Collections.<Dialog>copy(windowList, jDialogList);
Collections.copy(windowList, jDialogList);
34
35. Generic Constructors
• Constructors with type parameters are referred to
as generic constructors .
• They can exist within both generic classes and
non-generic classes.
• The scope of a constructor’s type parameter is
restricted to the constructor itself.
• Use when dependencies exist among the types
of one or more arguments.
• Just as with generic methods, type parameters of
generic constructors need not be provided
explicitly when invoked.
35
37. Custom Generic Classes
• Generic classes use the following syntax…
class className<typeParameters> {
...
}
• Below is the syntax for declaring a reference to a
generic class.
className<typeArguments> variableName =
new className<typeArguments>(constructorArguments);
Note: See GenericExample class.
37
38. Generic Interfaces
• As demonstrated earlier with List and Map,
interfaces can also be generic.
public interface List<E> extends Collection<E> {
boolean addAll(int index, Collection<? extends E> c);
ListIterator<E> listIterator(int index);
E set(int index, E element);
}
• The syntax is the same as with classes…
interface interfaceName<typeParameters> {
...
}
38
39. Generic Class Hierarchies
• The only difference between generic and non-
generic hierarchies is that generic hierarchies
require type variables be passed up the
inheritance tree accordingly.
• There are a few simple rules…
– Generic classes can subclass other generic classes
and non-generic classes.
– Non-generic classes cannot subclass generic classes.
• The reason is that a non-generic subclass cannot receive
type parameters and therefore cannot pass anything to
its generic superclass.
– Subclasses are free to add their own parameters not
required by the generic superclass. 39
40. Generic Restrictions
• Type parameters cannot be instantiated.
// Wrong! (Can’t create an instance of T)
objectReference = new T();
• Static members cannot use a type parameter
declared by the enclosing class.
– However, you can declare static generic methods that
define their own type parameters.
• A generic class cannot extend Throwable. This
means that you cannot create generic exception
classes.
40
41. Restrictions Continued...
• It is not possible to instantiate an array whose
base type is a type parameter.
– The reason is that T does not exist at runtime, so there
is no way for the compiler to know what type of array to
actually create.
public class Generic<T extends Number> {
T[] array;
Generic(T[] numbers) {
array = new T[10]; // Can’t create an array of T
array = numbers; // Reference assignments are fine
}
}
41
42. Restrictions Continued...
• It is also not possible to create an array of type-
specific generic references.
– Arrays of specific generic types simply aren’t allowed
because they can lead to a loss of type safety.
// Wrong! (Type-specific generic references)
Generic<Integer>[] array = new Generic<Integer>[10];
// Okay! (Wildcards are acceptable)
Generic<?>[] array = new Generic<?>[10];
42
44. Converting Legacy To Generics
• Make certain that the generic API is not unduly
restrictive; it must continue to support the original
contract of the API.
• You also need to ensure that the revised API
retains binary compatibility with old clients. This
implies that the erasure of the API must be the
same as the original, ungenerified API. In most
cases, this falls out naturally, but there are some
subtle cases.
44
46. Generics In Context
• Because generics are such a fundamental
change to the language, it’s important to have a
solid understanding of them.
• Most interaction with generics will happen as a
result of using the collections framework.
• For the most part, application developers won’t
write a lot of custom generics, but will use the
generics provided by various frameworks.
• The heavy writing of custom generics seems to
be most applicable for framework development.
46
47. Drawbacks Of Generics
• Steep learning curve
– Generics are a fundamental change and require a
completely different mindset.
– Conceptually, generics can be difficult to grasp and
can lead to code that is difficult to read and follow.
• Added complexity
– Many left C++ because of it’s complexity, some would
argue that Java is headed down that same road.
– Much of Java’s original appeal was its simplicity. Some
believe that the trade-off for type safety is not worth the
added complexity introduced by generics.
47
49. Summary
• Generics are a powerful extension to the Java
language because they streamline the creation of
type-safe, reusable code.
• Within generics, automatic casting and type
converting are provided by the compiler.
• The Collections framework has been entirely
reworked.
– Now collections can be restricted and guaranteed to
only hold the specified type, passed as a parameter.
– The need for type casting when retrieving an element
from a collection has been entirely eliminated.
49
50. Summary Continued...
• Generics provide backward compatibility by
enabling generic types to be used without type
variables (raw types).
– It is strongly recommended; however, that new
applications avoid using raw types as future releases
of Java may not support them (according to the Java
Language Specification).
50