Generics constitute the most significant change in the Java programming language since the 1.0 release. The addition of generics to JDK 5.0 is the result of one of the first Java Specification Requests, JSR 14, that was formulated in 1999. The expert group spent about five years on specifications and test implementations.
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. Generics are particularly useful for collection classes, such as the ubiquitous ArrayList
.
Generics are—at least on the surface—similar to templates in C++. In C++, as in Java, templates were first added to the language to support strongly typed collections. However, over the years, other uses were discovered. After reading this chapter, perhaps you will find novel uses for Java generics in your programs.
Generic programming means to write code that can be reused for objects of many different types. For example, you don’t want to program separate classes to collect String
and File
objects. And you don’t have to—the single class ArrayList
collects objects of any class. This is one example of generic programming.
Before JDK 5.0, generic programming in Java was always achieved with inheritance. The ArrayList
class simply maintained an array of Object
references:
public class ArrayList // before JDK 5.0 { public Object get(int i) { . . . } public void add(Object o) { . . . } . . . private Object[] elementData; }
This approach has two problems. A cast is necessary whenever you retrieve a value:
ArrayList files = new ArrayList();
. . .
String filename = (String) names.get(0);
Moreover, there is no error checking. You can add values of any class:
files.add(new File(". . ."));
This call compiles and runs without error. Elsewhere, casting the result of get
to a String
will cause an error.
JDK 5.0 offers a better solution: type parameters. The ArrayList
class now has a type parameter that indicates the element type:
ArrayList<String> files = new ArrayList<String>();
This makes your code easier to read. You can tell right away that this particular array list contains String
objects.
The compiler can make good use of this information too. No cast is required for calling get
: the compiler knows that the return type is String
, not Object
:
String filename = files.get(0);
The compiler also knows that the add
method of an ArrayList<String>
has a parameter of type String
. That is a lot safer than having an Object
parameter. Now the compiler can check that you don’t insert objects of the wrong type. For example, the statement
files.add(new File(". . .")); // can only add String objects to an ArrayList<String>
will not compile. A compiler error is much better than a class cast exception at run time.
This is the appeal of type parameters: they make your programs easier to read and safer.
It is easy to use a generic class such as ArrayList
. Most Java programmers will simply use types such as ArrayList<String>
as if they had been built into the language, just like String[]
arrays. (Of course, array lists are better than arrays because they can expand automatically.)
However, it is not so easy to implement a generic class. The programmers who use your code will want to plug in all sorts of classes for your type parameters. They expect everything to work without onerous restrictions and confusing error messages. Your job as a generic programmer, therefore, is to anticipate all the potential future uses of your class.
How hard can this get? Here is a typical issue that the designers of the standard class library had to grapple with. The ArrayList
class has a method addAll
to add all elements of another collection. A programmer may want to add all elements from an ArrayList<Manager>
to an ArrayList<Employee>
. But, of course, doing it the other way around should not be legal. How do you allow one call and disallow the other? The Java language designers invented an ingenious new concept, the wildcard type, to solve this problem. Wildcard types are rather abstract, but they allow a library builder to make methods as flexible as possible.
Generic programming falls into three skill levels. At a basic level, you just use generic classes—typically, collections such as ArrayList
—without thinking how and why they work. Most application programmers will want to stay at that level until something goes wrong. You may encounter a confusing error message when mixing different generic classes, or when interfacing with legacy code that knows nothing about type parameters. At that point, you need to learn enough about Java generics to solve problems systematically rather than through random tinkering. Finally, of course, you may want to implement your own generic classes and methods.
Application programmers probably won’t write lots of generic code. The folks at Sun have already done the heavy lifting and supplied type parameters for all the collection classes. As a rule of thumb, only code that traditionally involved lots of casts from very general types (such as Object
or the Comparable
interface) will benefit from using type parameters.
In this chapter, we tell you everything you need to know to implement your own generic code. However, we expect most readers to use this knowledge primarily for help with troubleshooting, and to satisfy their curiosity about the inner workings of the parameterized collection classes.
A generic class is a class with one or more type variables. In this chapter, we use a simple Pair
class as an example. This class allows us to focus on generics without being distracted by data storage details. Here is the code for the generic Pair
class:
public class Pair<T> { public Pair() { first = null; second = null; } public Pair(T first, T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; } private T first; private T second; }
The Pair
class introduces a type variable T
, enclosed in angle brackets < >
, after the class name. A generic class can have more than one type variable. For example, we could have defined the Pair
class with separate types for the first and second field:
public class Pair<T, U> { . . . }
The type variables are used throughout the class definition to specify method return types and the types of fields and local variables. For example,
private T first; // uses type variable
It is common practice to use uppercase letters for type variables, and to keep them short. The Java library uses the variable E
for the element type of a collection, K
and V
for key and value types of a table, and T
(and the neighboring letters U
and S
, if necessary) for “any type at all”.
You instantiate the generic type by substituting types for the type variables, such as
Pair<String>
You can think of the result as an ordinary class with constructors
Pair<String>() Pair<String>(String, String)
and methods
String getFirst() String getSecond() void setFirst(String) void setSecond(String)
In other words, the generic class acts as a factory for ordinary classes.
The program in Example 13–1 puts the Pair
class to work. The static minmax
method traverses an array and simultaneously computes the minimum and maximum value. It uses a Pair
object to return both results. Recall that the compareTo
method compares two strings, returning 0 if the strings are identical, a negative integer if the first string comes before the second in dictionary order, and a positive integer otherwise.
Superficially, generic classes in Java are similar to template classes in C++. The only obvious difference is that Java has no special template
keyword. However, as you will see throughout this chapter, there are substantial differences between these two mechanisms.
Example 13–1. PairTest1.java
1. public class PairTest1 2. { 3. public static void main(String[] args) 4. { 5. String[] words = { "Mary", "had", "a", "little", "lamb" }; 6. Pair<String> mm = ArrayAlg.minmax(words); 7. System.out.println("min = " + mm.getFirst()); 8. System.out.println("max = " + mm.getSecond()); 9. } 10. } 11. 12. class ArrayAlg 13. { 14. /** 15. Gets the minimum and maximum of an array of strings. 16. @param a an array of strings 17. @return a pair with the min and max value, or null if a is 18. null or empty 19. */ 20. public static Pair<String> minmax(String[] a) 21. { 22. if (a == null || a.length == 0) return null; 23. String min = a[0]; 24. String max = a[0]; 25. for (int i = 1; i < a.length; i++) 26. { 27. if (min.compareTo(a[i]) > 0) min = a[i]; 28. if (max.compareTo(a[i]) < 0) max = a[i]; 29. } 30. return new Pair<String>(min, max); 31. } 32. }
In the preceding section, you have seen how to define a generic class. You can also define a single method with type parameters.
class ArrayAlg
{
public static <T> T getMiddle(T[] a)
{
return a[a.length / 2]);
}
}
This method is defined inside an ordinary class, not inside a generic class. However, it is a generic method, as you can see from the angle brackets and the type variable. Note that the type variables are inserted after the modifiers (public static
, in our case) and before the return type.
You can define generic methods both inside ordinary classes and inside generic classes.
When you call a generic method, you can place the actual types, enclosed in angle brackets, before the method name:
String[] names = { "John", "Q.", "Public" }; String middle = ArrayAlg.<String>getMiddle(names);
In this case (and indeed in most cases), you can omit the <String>
type parameter from the method call. The compiler has enough information to infer the method that you want. It matches the type of names
(that is, String[]
) against the generic type T[]
and deduces that T
must be String
. That is, you can simply call
String middle = ArrayAlg.getMiddle(names);
Sometimes, a class or a method needs to place restrictions on type variables. Here is a typical example. We want to compute the smallest element of an array:
class ArrayAlg { public static <T> T min(T[] a) // almost correct { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 1; i < a.length; i++) if (smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; } }
But there is a problem. Look inside the code of the min
method. The variable smallest
has type T
, which means that it could be an object of an arbitrary class. How do we know that the class to which T
belongs has a compareTo
method?
The solution is to restrict T
to a class that implements the Comparable
interface—a standard interface with a single method, compareTo
. You achieve this by giving a bound for the type variable T
:
public static <T extends Comparable> T min(T[] a) . . .
Actually, the Comparable
interface is itself a generic type. For now, we will ignore that complexity.
Now, the generic min
method can only be called with arrays of classes that implement the Comparable
interface, such as String
, Date
, and so on. Calling min
with a Rectangle
array is a compile-time error because the Rectangle
class does not implement Comparable
.
In C++, you cannot restrict the types of template parameters. If a programmer instantiates a template with an inappropriate type, an (often obscure) error message is reported inside the template code.
You may wonder why you use the extends
keyword rather than the implements
keyword in this situation—after all, Comparable
is an interface. The notation
<T extends
BoundingType>
expresses that T
should be a subtype of the bounding type. Both T
and the bounding type can be either a class or an interface. The extends
keyword was chosen because it is a reasonable approximation of the subtype concept, and the Java designers did not want to add a new keyword (such as sub
) to the language.
A type variable or wildcard can have multiple bounds, for example,
T extends Comparable & Serializable
The bounding types are separated by ampersands (&
) because commas are used to separate type variables.
As with Java inheritance, you can have as many interface supertypes as you like, but at most one of the bounds can be a class. If you have a class as a bound, it must be the first one in the bounds list.
In the next sample program (Example 13–2), we rewrite the minmax
method to be generic. The method computes the minimum and maximum of a generic array, returning a Pair<T>
.
Example 13–2. PairTest2.java
1. import java.util.*; 2. 3. public class PairTest2 4. { 5. public static void main(String[] args) 6. { 7. GregorianCalendar[] birthdays = 8. { 9. new GregorianCalendar(1906, Calendar.DECEMBER, 9), // G. Hopper 10. new GregorianCalendar(1815, Calendar.DECEMBER, 10), // A. Lovelace 11. new GregorianCalendar(1903, Calendar.DECEMBER, 3), // J. von Neumann 12. new GregorianCalendar(1910, Calendar.JUNE, 22), // K. Zuse 13. }; 14. Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays); 15. System.out.println("min = " + mm.getFirst().getTime()); 16. System.out.println("max = " + mm.getSecond().getTime()); 17. } 18. } 19. 20. class ArrayAlg 21. { 22. /** 23. Gets the minimum and maximum of an array of objects of type T. 24. @param a an array of objects of type T 25. @return a pair with the min and max value, or null if a is 26. null or empty 27. */ 28. public static <T extends Comparable> Pair<T> minmax(T[] a) 29. { 30. if (a == null || a.length == 0) return null; 31. T min = a[0]; 32. T max = a[0]; 33. for (int i = 1; i < a.length; i++) 34. { 35. if (min.compareTo(a[i]) > 0) min = a[i]; 36. if (max.compareTo(a[i]) < 0) max = a[i]; 37. } 38. return new Pair<T>(min, max); 39. } 40. }
The virtual machine does not have objects of generic types—all objects belong to ordinary classes. An earlier version of the generics implementation was even able compile a program that uses generics into class files that executed on 1.0 virtual machines! This backward compatibility was only abandoned fairly late in the development for Java generics. If you use the Sun compiler to compile code that uses Java generics, the resulting class files will not execute on pre-5.0 virtual machines.
If you want to have the benefits of the JDK 5.0 language features while retaining bytecode compatibility with older virtual machines, check out http://sourceforge.net/projects/retroweaver. The Retroweaver program rewrites class files so that they are compatible with older virtual machines.
Whenever you define a generic type, a corresponding raw type is automatically provided. The name of the raw type is simply the name of the generic type, with the type parameters removed. The type variables are erased and replaced by their bounding types (or Object
for variables without bounds.)
For example, the raw type for Pair<T>
looks like this:
public class Pair { public Pair(Object first, Object second) { this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object newValue) { first = newValue; } public void setSecond(Object newValue) { second = newValue; } private Object first; private Object second; }
Because T
is an unbounded type variable, it is simply replaced by Object
.
The result is an ordinary class, just as you might have implemented it before generics were added to the Java programming language.
Your programs may contain different kinds of Pair
, such as Pair<String>
or Pair<GregorianCalendar>
, but erasure turns them all into raw Pair
types.
In this regard, Java generics are very different from C++ templates. C++ produces different types for each template instantiation, a phenomenon called “template code bloat.” Java does not suffer from this problem.
The raw type replaces type variables with the first bound, or Object
if no bounds are given. For example, the type variable in the class Pair<T>
has no explicit bounds, hence the raw type replaces T
with Object
. Suppose we declare a slightly different type
public class Interval<T extends Comparable & Serializable> implements Serializable { public Interval(T first, T second) { if (first.compareTo(second) <= 0) { lower = first; upper = second; } else { lower = second; upper = first; } } . . . private T lower; private T upper; }
The raw type Interval
looks like this:
public class Interval implements Serializable { public Interval(Comparable first, Comparable second) { . . . } . . . private Comparable lower; private Comparable upper; }
You may wonder what happens if you switch the bounds: class Interval<Serializable & Comparable>
. In that case, the raw type replaces T
with Serializable
, and the compiler inserts casts to Comparable
when necessary. For efficiency, you should therefore put tagging interfaces (that is, interfaces without methods) at the end of the bounds list.
When you program a call to a generic method, the compiler inserts casts when the return type has been erased. For example, consider the sequence of statements
Pair<Employee> buddies = . . .; Employee buddy = buddies.getFirst();
The erasure of getFirst
has return type Object
. The compiler automatically inserts the cast to Employee
. That is, the compiler translates the method call into two virtual machine instructions:
a call to the raw method Pair.getFirst
a cast of the returned Object
to the Employee
type
Casts are also inserted when you access a generic field. Suppose the first
and second
fields of the Pair
class were public. (Not a good programming style, perhaps, but it is legal Java.) Then the expression
Employee buddy = buddies.first;
also has a cast inserted in the resulting byte codes.
Type erasure also happens for generic methods. Programmers usually think of a generic method such as
public static <T extends Comparable> T min(T[] a)
as a whole family of methods, but after erasure, only a single method is left:
public static Comparable min(Comparable[] a)
Note that the type parameter T
has been erased, leaving only its bounding type Comparable
.
Erasure of method brings up a couple of complexities. Consider this example:
class DateInterval extends Pair<Date> { public void setSecond(Date second) { if (second.compareTo(getFirst()) >= 0) super.setSecond(second); } . . . }
A date interval is a pair of Date
objects, and we’ll want to override the methods to ensure that the second value is never smaller than the first. This class is erased to
class DateInterval extends Pair // after erasure { public void setSecond(Date second) { . . . } . . . }
Perhaps surprisingly, there is another setSecond
method, inherited from Pair
, namely,
public void setSecond(Object second)
This is clearly a different method because it has a parameter of a different type—Object
instead of Date
. But it shouldn’t be different. Consider this sequence of statements:
DateInterval interval = new DateInterval(. . .); Pair<Date> pair = interval; // OK--assignment to superclass pair.setSecond(aDate);
Our expectation is that the call to setSecond
is polymorphic and that the appropriate method is called. Because pair
refers to a DateInterval
object, that should be DateInterval.setSecond
. The problem is that the type erasure interferes with polymorphism. To fix this problem, the compiler generates a bridge method in the DateInterval
class:
public void setSecond(Object second) { setSecond((Date) second); }
To see why this works, let us carefully follow the execution of the statement
pair.setSecond(aDate)
The variable pair
has declared type Pair<Date>
, and that type only has a single method called setSecond
, namely setSecond(Object)
. The virtual machine calls that method on the object to which pair
refers. That object is of type DateInterval
. Therefore, the method DateInterval.setSecond(Object)
is called. That method is the synthesized bridge method. It calls DateInterval.setSecond(Date)
, which is what we want.
Bridge methods can get even stranger. Suppose the DateInterval
method also overrides the getSecond
method:
class DateInterval extends Pair<Date> { public Date getSecond() { return (Date) super.getSecond().clone(); } . . . }
In the erased type, there are two getSecond
methods:
Date getSecond() // defined in DateInterval Object getSecond() // defined in Pair
You could not write Java code like that—it would be illegal to have two methods with the same parameter types—here, no parameters. However, in the virtual machine, the parameter types and the return type specify a method. Therefore, the compiler can produce bytecodes for two methods that differ only in their return type, and the virtual machine will handle this situation correctly.
Bridge methods are not limited to generic types. We already noted in Chapter 5 that, starting with JDK 5.0, it is legal for a method to specify a more restrictive return type when overriding another method. For example,
public class Employee implements Cloneable { public Employee clone() throws CloneNotSupportedException { ... } }
The Object.clone
and Employee.clone
methods are said to have covariant return types.
Actually, the Employee
class has two clone methods:
Employee clone() // defined above Object clone() // synthesized bridge method, overrides Object.clone
The synthesized bridge method calls the newly defined method.
In summary, you need to remember these facts about translation of Java generics:
There are no generics in the virtual machines, only ordinary classes and methods.
All type parameters are replaced by their bounds.
Bridge methods are synthesized to preserve polymorphism.
Casts are inserted as necessary to preserve type safety.
Lots of Java code was written before JDK 5.0. If generic classes could not interoperate with that code, they would probably not be widely used. Fortunately, it is straightforward to use generic classes together with their raw equivalents in legacy APIs.
Let us look at a concrete example. To set the labels of a JSlider
, you use the method
void setLabelTable(Dictionary table)
In Chapter 9, we used the following code to populate the label table:
Dictionary<Integer, Component> labelTable = new Hashtable<Integer, Component>(); labelTable.put(0, new JLabel(new ImageIcon("nine.gif"))); labelTable.put(20, new JLabel(new ImageIcon("ten.gif"))); . . . slider.setLabelTable(labelTable); // WARNING
The Hashtable
class is a concrete subclass of the abstract Dictionary
class. Both Dictionary
and Hashtable
have been declared as “obsolete” ever since they were superseded by the Map
interface and the HashMap
class of JDK 1.2. Apparently though, they are still alive and kicking. After all, the JSlider
class was only added in JDK 1.3. Didn’t its programmers know about the Map
class by then? Does this make you hopeful that they are going to adopt generics in the near future? Well, that’s the way it goes with legacy code.
In JDK 5.0, the Dictionary
and Hashtable
classes were turned into a generic class. Therefore, we are able to form Dictionary<Integer, Component>
instead of using a raw Dictionary
. However, when you pass the Dictionary<Integer, Component>
object to setLabelTable
, the compiler issues a warning.
Dictionary<Integer, Components> labelTable = . . .; slider.setLabelTable(labelTable); // WARNING
After all, the compiler has no assurance about what the setLabelTable
might do to the Dictionary
object. That method might replace all the keys with strings. That breaks the guarantee that the keys have type Integer
, and future operations may cause bad cast exceptions.
There isn’t much you can do with this warning, except ponder it and ask what the JSlider
is likely going to do with this Dictionary
object. In our case, it is pretty clear that the JSlider
only reads the information, so we can ignore the warning.
Now consider the opposite case, in which you get an object of a raw type from a legacy class. You can assign it to a parameterized type variable, but of course you will get a warning. For example,
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // WARNING
That’s ok—review the warning and make sure that the label table really contains Integer
and Component
objects. Of course, there never is an absolute guarantee. A malicious coder might have installed a different Dictionary
in the slider. But again, the situation is no worse than it was before JDK 5.0. In the worst case, your program will throw an exception.
It is unfortunate that you can’t turn off the warnings after you reviewed them. It seems impractical to review every warning every time you recompile a source file. The Java language designers are planning to add a more flexible warning mechanism to a future version of the JDK.
In the following sections, we discuss a number of restrictions that you need to consider when working with Java generics. Most of these restrictions are a consequence of type erasure.
You cannot substitute a primitive type for a type parameter. Thus, there is no Pair<double>
, only Pair<Double>
. The reason is, of course, type erasure. After erasure, the Pair
class has fields of type Object
, and you can’t use them to store double
values.
This is an annoyance, to be sure, but it is consistent with the separate status of primitive types in the Java language. It is not a fatal flaw—there are only eight primitive types, and you can always handle them with separate classes and methods when wrapper types are not an acceptable substitute.
Objects in the virtual machine always have a specific nongeneric type. Therefore, all type inquiries yield only the raw type. For example,
if (a instanceof Pair<String>) // same as a instanceof Pair
really only tests whether a
is a Pair
of any type. The same is true for the test
if (a instanceof Pair<T>) // T is ignored
or the cast
Pair<String> p = (Pair<String>) a; // WARNING--can only test that a is a Pair
To remind you of the risk, you will get a compiler warning whenever you use instanceof
or cast expressions that involve generic types.
In the same spirit, the getClass
method always returns the raw type. For example,
Pair<String> stringPair = . . .; Pair<Employee> employeePair = . . .; if (stringPair.getClass() == employeePair.getClass()) // they are equal
The comparison yields true
because both calls to getClass
return Pair.class
.
You can neither throw nor catch objects of a generic class. In fact, it is not even legal for a generic class to extend Throwable
. For example, the following definition will not compile:
public class Problem<T> extends Exception { /* . . . */ } // ERROR--can't extend Throwable
You cannot use a type variable in a catch
clause. For example, the following method will not compile:
public static <T extends Throwable> void doWork(Class<T> t)
{
try
{
do work }
catch (T e) // ERROR--can't catch type variable
{
Logger.global.info(...)
}
}
However, it is ok to use type variables in exception specifications. The following method is legal:
public static <T extends Throwable> void doWork(T t) throws T // OK
{
try
{
do work }
catch (Throwable realCause)
{
t.initCause(realCause);
throw t;
}
}
You cannot declare arrays of parameterized types, such as
Pair<String>[] table = new Pair<String>(10); // ERROR
What’s wrong with that? After erasure, the type of table
is Pair[]
. You can convert it to Object[]
:
Object[] objarray = table;
We discussed in Chapter 5 that an array remembers its component type and throws an ArrayStoreException
if you try to store an element of the wrong type:
objarray[0] = "Hello"; // ERROR--component type is Pair
But erasure renders this mechanism ineffective for generic types. The assignment
objarray[0] = new Pair<Employee>();
would pass the array store check but still result in a type error. For this reason, arrays of parameterized types are outlawed.
You cannot instantiate generic types. For example, the following Pair<T>
constructor is illegal:
public Pair() { first = new T(); second = new T(); } // ERROR
Type erasure would change T
to Object
, and surely you don’t want to call new Object()
.
Similarly, you cannot make a generic array:
public <T> T[] minMax(T[] a) { T[] mm = new T[2]; . . . } // ERROR
Type erasure would cause this method to always construct an array Object[2]
.
However, you can construct generic objects and arrays through reflection, by calling the Class.newInstance
and Array.newInstance
methods.
You cannot reference type variables in static fields or methods. For example, the following clever idea won’t work:
public class Singleton<T>
{
public static T getSingleInstance() // ERROR
{
if (singleInstance == null)
construct new instance of T
return singleInstance;
}
private static T singleInstance; // ERROR
}
If this could be done, then a program could declare a Singleton<Random>
to share a random number generator and a Singleton<JFileChooser>
to share a file chooser dialog. But it can’t work. After type erasure there is only one Singleton
class, and only one singleInstance
field. For that reason, static fields and methods with type variables are simply outlawed.
It is illegal to create conditions that cause clashes when generic types are erased. Here is an example. Suppose we add an equals
method to the Pair
class, like this:
public class Pair<T> { public boolean equals(T value) { return first.equals(value) && second.equals(value); } . . . }
Consider a Pair<String>
. Conceptually, it has two equals
methods:
boolean equals(String) // defined in Pair<T> boolean equals(Object) // inherited from Object
But the intuition leads us astray. The erasure of the method
boolean equals(T)
is
boolean equals(Object)
which clashes with the Object.equals
method.
The remedy is, of course, to rename the offending method.
The generics specification cites another rule: “To support translation by erasure, we impose the restriction that a class or type variable may not at the same time be a subtype of two interface types which are different parameterizations of the same interface.” For example, the following is illegal:
class Calendar implements Comparable<Calendar> { . . . } class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> { . . . } // ERROR
GregorianCalendar
would then implement both Comparable<Calendar>
and Comparable<GregorianCalendar>
, which are different parameterizations of the same interface.
It is not clear what this restriction has to do with type erasure. The nongeneric version
class Employee implements Comparable { . . . } class Manager extends Employee implements Comparable { . . . }
is legal.
When you work with generic classes, you need to learn a few rules about inheritance and subtypes. Let’s start with a situation that many programmers find unintuitive. Consider a class and a subclass, such as Employee
and Manager
. Is Pair<Manager>
a subclass of Pair<Employee>
? Perhaps surprisingly, the answer is “no.” For example, the following code will not compile
Manager[] topHonchos = . . .; Pair<Employee> = ArrayAlg.minmax(topHonchos); // ERROR
The minmax
method returns a Pair<Manager>
, not a Pair<Employee>
, and it is illegal to assign one to the other.
In general, there is no relationship between Pair<S>
and Pair<T>
, no matter how S
and T
are related (see Figure 13–1).
This seems like a cruel restriction, but it is necessary for type safety. Suppose we were allowed to convert a Pair<Manager>
to a Pair<Employee>
. Consider this code.
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair<Employee> employeeBuddies = managerBuddies; // illegal, but suppose it wasn't employeeBuddies.setFirst(lowlyEmployee);
Clearly, the last statement is legal. But employeeBuddies
and managerBuddies
refer to the same object. We now managed to pair up the CFO with a lowly employee, which should not be possible for a Pair<Manager>
.
In contrast, you can always convert a parameterized type to a raw type. For example, Pair<Employee>
is a subtype of the raw type Pair
. This conversion is necessary for interfacing with legacy code.
Can you convert to the raw type and then cause a type error? Unfortunately, you can. Consider this example:
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair rawBuddies = managerBuddies; // OK rawBuddies.setFirst(new File(". . .")); // only a compile-time warning
This sounds scary. However, keep in mind that you are no worse off than you were with older versions of Java. The security of the virtual machine is not at stake. When the foreign object is retrieved with getFirst
and assigned to a Manager
variable, a ClassCastException
is thrown, just as in the good old days. You merely lose the added safety that generic programming normally provides.
Finally, generic classes can extend or implement other generic classes. In this regard, they are no different from ordinary classes. For example, the class ArrayList<T>
implements the interface List<T>
. That means, an ArrayList<Manager>
can be converted to a List<Manager>
. However, as you just saw, an ArrayList<Manager>
is not an ArrayList<Employee>
or List<Employee>
. Figure 13–2 shows these relationships.
It was known for some time among researchers of type systems that a rigid system of generic types is quite unpleasant to use. The Java designers invented an ingenious (but nevertheless safe) “escape hatch”: the wildcard type. For example, the wildcard type
Pair<? extends Employee>
denotes any generic Pair
type whose type parameter is a subclass of Employee
, such as Pair<Manager>
, but not Pair<String>
.
Let’s say you want to write a method that prints out pairs of employees, like this:
public static void printBuddies(Pair<Employee> p) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName() + " and " + second.getName() + " are buddies."; }
As you saw in the preceding section, you cannot pass a Pair<Manager>
to that method, which is rather limiting. But the solution is simple: use a wildcard type:
public static void printBuddies(Pair<? extends Employee> p)
The type Pair<Manager>
is a subtype of Pair<? extends Employee>
(see Figure 13–3).
Can we use wildcards to corrupt a Pair<Manager>
through a Pair<? extends Employee>
reference?
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK wildcardBuddies.setFirst(lowlyEmployee); // compile-time error
No corruption is possible. The call to setFirst
is a type error. To see why, let us have a closer look at the type Pair<? extends Employee>
. Its methods look like this:
? extends Employee getFirst() void setFirst(? extends Employee)
This makes it impossible to call the setFirst
method. The compiler only knows that it needs some subtype of Employee
, but it doesn’t know which type. It refuses to pass any specific type—after all, ?
might not match it.
We don’t have this problem with getFirst
: It is perfectly legal to assign the return value of getFirst
to an Employee
reference.
This is the key idea behind bounded wildcards. We now have a way of distinguishing between the safe accessor methods and the unsafe mutator methods.
Wildcard bounds are similar to type variable bounds, but they have an added capability— you can specify a supertype bound, like this:
? super Manager
This wildcard is restricted to all supertypes of Manager
. (It was a stroke of good luck that the existing super
keyword describes the relationship so accurately.)
Why would you want to do this? A wildcard with a supertype bound gives you the opposite behavior of the wildcards described on page 721. You can supply parameters to methods, but you can’t use the return values. For example, Pair<? super Manager>
has methods
void set(? super Manager) ? super Manager get()
The compiler doesn’t know the exact type of the set
method but can call it with any Manager
object (or a subtype such as Executive
), but not with an Employee
. However, if you call get
, there is no guarantee about the type of the returned object. You can only assign it to an Object
.
Here is a typical example. We have an array of managers and want to put the manager with the lowest and highest bonus into a Pair
object. What kind of Pair
? A Pair<Employee>
should be fair game or, for that matter, a Pair<Object>
(see Figure 13–4). The following method will accept any appropriate Pair
:
public static void minMaxBonus(Manager[] a, Pair<? super Manager> result) { if (a == null || a.length == 0) return; Manager min = a[0]; Manager max = a[0]; for (int i = 1; i < a.length; i++) { if (min.getBonus() > a[i].getBonus()) min = a[i]; if (max.getBonus() < a[i].getBonus()) max = a[i]; } result.setFirst(min); result.setSecond(max); }
Intuitively speaking, wildcards with supertype bounds let you write to a generic object, wildcards with subtype bounds let you read from a generic objects.
Here is another use for supertype bounds. The Comparable
interface is itself a generic type. It is declared as follows:
public interface Comparable<T> { public int compareTo(T other); }
Here, the type variable indicates the type of the other
parameter. For example, the String
class implements Comparable<String>
, and its compareTo
method is declared as
public int compareTo(String other)
This is nice—the explicit parameter has the correct type. Before JDK 5.0, other
was an Object
, and a cast was necessary in the implementation of the method.
Because Comparable
is a generic type, perhaps we should have done a better job with the min
method of the ArrayAlg
class? We could have declared it as
public static <T extends Comparable<T>> T min(T[] a) . . .
This looks more thorough than just using T extends Comparable
, and it would work fine for many classes. For example, if you compute the minimum of a String
array, then T
is the type String
, and String
is a subtype of Comparable<String>
. But we run into a problem when processing an array of GregorianCalendar
objects. As it happens, GregorianCalendar
is a subclass of Calendar
, and Calendar
implements Comparable<Calendar>
. Thus, GregorianCalendar
implements Comparable<Calendar>
but not Comparable<GregorianCalendar>
.
In a situation such as this one, supertypes come to the rescue:
public static <T extends Comparable<? super T>> T min(T[] a) . . .
Now the compareTo
method has the form
int compareTo(? super T)
Maybe it is declared to take an object of type T
, or—for example, when T
is GregorianCalendar
— a supertype of T
. At any rate, it is safe to pass an object of type T
to the compareTo
method.
To the uninitiated, a declaration such as <T
extends Comparable<? super T>
>
is bound to look intimidating. This is unfortunate, because the intent of this declaration is to help application programmers by removing unnecessary restrictions on the call parameters. Application programmers with no interest in generics will probably learn quickly to gloss over these declarations and just take for granted that library programmers will do the right thing. If you are a library programmer, you’ll need to get used to wildcards, or your users will curse you and throw random casts at their code until it compiles.
You can even use wildcards with no bounds at all, for example, Pair<?>
. At first glance, this looks identical to the raw Pair
type. Actually, the types are very different. The type Pair<?>
has methods such as
? getFirst() void setFirst(?)
The return value of getFirst
can only be assigned to an Object
. The setFirst
method can never be called, not even with an Object
. That’s the essential difference between Pair<?>
and Pair
: you can call the setObject
method of the raw Pair
class with any Object
.
Why would you ever want such a wimpy type? It is useful for very simple operations. For example, the following method tests whether a pair contains a given object. It never needs the actual type.
public static boolean hasNulls(Pair<?> p) { return p.getFirst() == null || p.getSecond() == null; }
You could have avoided the wildcard type by turning contains
into a generic method:
public static <T> boolean hasNulls(Pair<T> p)
However, the version with the wildcard type seems easier to read.
Let us write a method that swaps the elements of a pair:
public static void swap(Pair<?> p)
A wildcard is not a type variable, so we can’t write code that uses ?
as a type. In other words, the following would be illegal:
? t = p.getFirst(); // ERROR p.setFirst(p.getSecond()); p.setSecond(t);
That’s a problem because we need to temporarily hold the first element when we do the swapping. Fortunately, there is an interesting solution to this problem. We can write a helper method, swapHelper
, like this:
public static <T> void swapHelper(Pair<T> p) { T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); }
Note that swapHelper
is a generic method, whereas swap
is not—it has a fixed parameter of type Pair<?>
.
Now we can call swapHelper
from swap
:
public static void swap(Pair<?> p) { swapHelper(p); }
In this case, the parameter T
of the swapHelper
method captures the wildcard. It isn’t known what type the wildcard denotes, but it is a definite type, and the definition of <T>swapHelper
makes perfect sense when T
denotes that type.
Of course, in this case, we were not compelled to use a wildcard—there is nothing wrong with using a type parameter, as in the swapHelper
method. However, consider this example in which a wildcard type occurs naturally in the middle of a computation:
public static void maxMinBonus(Manager[] a, Pair<? super Manager> result) { minMaxBonus(a, result); PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type }
Here, the wildcard capture mechanism cannot be avoided.
Wildcard capture is only legal in very limited circumstances. The compiler must be able to guarantee that the wildcard represents a single, definite type. For example, the T
in ArrayList<Pair<T>>
can never capture the wildcard in ArrayList<Pair<?>>
. The array list might hold two Pair<?>
, each of which has a different type for ?
.
The test program in Example 13–3 gathers up the various methods that we discussed in the preceding sections, so that you can see them in context.
Example 13–3. PairTest3.java
1. import java.util.*; 2. 3. public class PairTest3 4. { 5. public static void main(String[] args) 6. { 7. Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15); 8. Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15); 9. Pair<Manager> buddies = new Pair<Manager>(ceo, cfo); 10. printBuddies(buddies); 11. 12. ceo.setBonus(1000000); 13. cfo.setBonus(500000); 14. Manager[] managers = { ceo, cfo }; 15. 16. Pair<Employee> result = new Pair<Employee>(); 17. minMaxBonus(managers, result); 18. System.out.println("first: " + result.getFirst().getName() 19. + ", second: " + result.getSecond().getName()); 20. maxMinBonus(managers, result); 21. System.out.println("first: " + result.getFirst().getName() 22. + ", second: " + result.getSecond().getName()); 23. } 24. 25. public static void printBuddies(Pair<? extends Employee> p) 26. { 27. Employee first = p.getFirst(); 28. Employee second = p.getSecond(); 29. System.out.println(first.getName() + " and " + second.getName() + " are buddies."); 30. } 31. 32. public static void minMaxBonus(Manager[] a, Pair<? super Manager> result) 33. { 34. if (a == null || a.length == 0) return; 35. Manager min = a[0]; 36. Manager max = a[0]; 37. for (int i = 1; i < a.length; i++) 38. { 39. if (min.getBonus() > a[i].getBonus()) min = a[i]; 40. if (max.getBonus() < a[i].getBonus()) max = a[i]; 41. } 42. result.setFirst(min); 43. result.setSecond(max); 44. } 45. 46. public static void maxMinBonus(Manager[] a, Pair<? super Manager> result) 47. { 48. minMaxBonus(a, result); 49. PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type 50. } 51. } 52. 53. class PairAlg 54. { 55. public static boolean hasNulls(Pair<?> p) 56. { 57. return p.getFirst() == null || p.getSecond() == null; 58. } 59. 60. public static void swap(Pair<?> p) { swapHelper(p); } 61. 62. public static <T> void swapHelper(Pair<T> p) 63. { 64. T t = p.getFirst(); 65. p.setFirst(p.getSecond()); 66. p.setSecond(t); 67. } 68. } 69. 70. class Employee 71. { 72. public Employee(String n, double s, int year, int month, int day) 73. { 74. name = n; 75. salary = s; 76. GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); 77. hireDay = calendar.getTime(); 78. } 79. 80. public String getName() 81. { 82. return name; 83. } 84. 85. public double getSalary() 86. { 87. return salary; 88. } 89. 90. public Date getHireDay() 91. { 92. return hireDay; 93. } 94. 95. public void raiseSalary(double byPercent) 96. { 97. double raise = salary * byPercent / 100; 98. salary += raise; 99. } 100. 101. private String name; 102. private double salary; 103. private Date hireDay; 104. } 105. 106. class Manager extends Employee 107. { 108. /** 109. @param n the employee's name 110. @param s the salary 111. @param year the hire year 112. @param month the hire month 113. @param day the hire day 114. */ 115. public Manager(String n, double s, int year, int month, int day) 116. { 117. super(n, s, year, month, day); 118. bonus = 0; 119. } 120. 121. public double getSalary() 122. { 123. double baseSalary = super.getSalary(); 124. return baseSalary + bonus; 125. } 126. 127. public void setBonus(double b) 128. { 129. bonus = b; 130. } 131. 132. public double getBonus() 133. { 134. return bonus; 135. } 136. 137. private double bonus; 138. }
The Class
class is now generic. For example, String.class
is actually an object (in fact, the sole object) of the class Class<String>
.
The type parameter is useful because it allows the methods of Class<T>
to be more specific about their return types. The following methods of Class<T>
take advantage of the type parameter:
T newInstance() T cast(Object obj) T[] getEnumConstants() Class<? super T> getSuperclass() Constructor<T> getConstructor(Class... parameterTypes) Constructor<T> getDeclaredConstructor(Class... parameterTypes)
The newInstance
method returns an instance of the class, obtained from the default constructor. Its return type can now be declared to be T
, the same type as the class that is being described by Class<T>
. That saves a cast.
The cast
method returns the given object, now declared as type T
if its type is indeed a subtype of T
. Otherwise, it throws a BadCastException
.
The getEnumConstants
method returns null
if this class is not an enum
class or an array of the enumeration values, which are known to be of type T.
Finally, the getConstructor
and getDeclaredConstructor
methods return a Constructor<T>
object. The Constructor
class has also been made generic so that its newInstance
method has the correct return type.
java.lang.Class<T> 1.0
T newInstance()
5.0
returns a new instance constructed with the default constructor.
T cast(Object obj)
5.0
returns obj
if it is null
or can be converted to the type T
, or throws a BadCastException
otherwise.
T[] getEnumConstants()
5.0
returns an array of all values if T
is an enumerated type, null
otherwise.
Class<? super T> getSuperclass()
5.0
returns the superclass of this class, or null
if T
is not a class or the class Object
.
Constructor<T> getConstructor(Class... parameterTypes)
5.0
Constructor<T> getDeclaredConstructor(Class... parameterTypes)
5.0
get the public constructor, or the constructor with the given parameter types.
java.lang.reflect.Constructor<T> 1.1
T newInstance(Object... parameters)
5.0
returns a new instance constructed with the given parameters.
It is sometimes useful to match the type variable of a Class<T>
parameter in a generic method. Here is the canonical example:
public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException { return new Pair<T>(c.newInstance(), c.newInstance()); }
If you call
makePair(Employee.class)
then Employee.class
is an object of type Class<Employee>
. The type parameter T
of the makePair
method matches Employee
, and the compiler can infer that the method returns a Pair<Employee>
.
One of the notable features of Java generics is the erasure of generic types in the virtual machine. Perhaps surprisingly, the erased classes still retain some faint memory of their generic origin. For example, the raw Pair
class knows that it originated from the generic class Pair<T>
, even though an object of type Pair
can’t tell whether it was constructed as a Pair<String>
or Pair<Employee>
.
Similarly, consider a method
public static Comparable min(Comparable[] a)
that is the erasure of a generic method
public static <T extends Comparable<? super T>> T min(T[] a)
You can use the reflection API enhancements of JDK 5.0 to determine that
the generic method has a type parameter called T
;
the type parameter has a subtype bound that is itself a generic type;
the bounding type has a wildcard parameter;
the wildcard parameter has a supertype bound; and
the generic method has a generic array parameter.
In other words, you get to reconstruct everything about generic classes and methods that their implementors declared. However, you won’t know how the type parameters were resolved for specific objects or method calls.
The type information that is contained in class files to enable reflection of generics is incompatible with older virtual machines.
In order to express generic type declarations, JDK 5.0 provides a new interface Type
in the java.lang.reflect
package. The interface has the following subtypes:
the Class
class, describing concrete types
the TypeVariable
interface, describing type variables (such as T extends Comparable<? super T>
)
the WildcardType
interface, describing wildcards (such as ? super T
)
the ParameterizedType
interface, describing generic class or interface types (such as Comparable<? super T>
)
the GenericArrayType
interface, describing generic arrays (such as T[]
)
Figure 13–5 shows the inheritance hierarchy. Note that the last four subtypes are interfaces—the virtual machine instantiates suitable classes that implement these interfaces.
Example 13–4 uses the generic reflection API to print out what it discovers about a given class. If you run it with the Pair
class, you get this report:
class Pair<T extends java.lang.Object> extends java.lang.Object public T extends java.lang.Object getSecond() public void setFirst(T extends java.lang.Object) public void setSecond(T extends java.lang.Object) public T extends java.lang.Object getFirst()
If you run it with ArrayAlg
in the PairTest2
directory, the report displays the following method:
public static <T extends java.lang.Comparable> Pair<T extends java.lang.Comparable> minmax (T extends java.lang.Comparable[])
The API notes at the end of this section describe the methods used in the example program.
Example 13–4. GenericReflectionTest.java
1. import java.lang.reflect.*; 2. import java.util.*; 3. 4. public class GenericReflectionTest 5. { 6. public static void main(String[] args) 7. { 8. // read class name from command-line args or user input 9. String name; 10. if (args.length > 0) 11. name = args[0]; 12. else 13. { 14. Scanner in = new Scanner(System.in); 15. System.out.println("Enter class name (e.g. java.util.Date): "); 16. name = in.next(); 17. } 18. 19. try 20. { 21. // print generic info for class and public methods 22. Class cl = Class.forName(name); 23. printClass(cl); 24. for (Method m : cl.getDeclaredMethods()) 25. printMethod(m); 26. } 27. catch (ClassNotFoundException e) 28. { 29. e.printStackTrace(); 30. } 31. } 32. 33. public static void printClass(Class cl) 34. { 35. System.out.print(cl); 36. printTypes(cl.getTypeParameters(), "<", ", ", ">"); 37. Type sc = cl.getGenericSuperclass(); 38. if (sc != null) 39. { 40. System.out.print(" extends "); 41. printType(sc); 42. } 43. printTypes(cl.getGenericInterfaces(), " implements ", ", ", ""); 44. System.out.println(); 45. } 46. 47. public static void printMethod(Method m) 48. { 49. String name = m.getName(); 50. System.out.print(Modifier.toString(m.getModifiers())); 51. System.out.print(" "); 52. printTypes(m.getTypeParameters(), "<", ", ", "> "); 53. 54. printType(m.getGenericReturnType()); 55. System.out.print(" "); 56. System.out.print(name); 57. System.out.print("("); 58. printTypes(m.getGenericParameterTypes(), "", ", ", ""); 59. System.out.println(")"); 60. } 61. 62. public static void printTypes(Type[] types, String pre, String sep, String suf) 63. { 64. if (types.length > 0) System.out.print(pre); 65. for (int i = 0; i < types.length; i++) 66. { 67. if (i > 0) System.out.print(sep); 68. printType(types[i]); 69. } 70. if (types.length > 0) System.out.print(suf); 71. } 72. 73. public static void printType(Type type) 74. { 75. if (type instanceof Class) 76. { 77. Class t = (Class) type; 78. System.out.print(t.getName()); 79. } 80. else if (type instanceof TypeVariable) 81. { 82. TypeVariable t = (TypeVariable) type; 83. System.out.print(t.getName()); 84. printTypes(t.getBounds(), " extends ", " & ", ""); 85. } 86. else if (type instanceof WildcardType) 87. { 88. WildcardType t = (WildcardType) type; 89. System.out.print("?"); 90. printTypes(t.getLowerBounds(), " extends ", " & ", ""); 91. printTypes(t.getUpperBounds(), " super ", " & ", ""); 92. } 93. else if (type instanceof ParameterizedType) 94. { 95. ParameterizedType t = (ParameterizedType) type; 96. Type owner = t.getOwnerType(); 97. if (owner != null) { printType(owner); System.out.print("."); } 98. printType(t.getRawType()); 99. printTypes(t.getActualTypeArguments(), "<", ", ", ">"); 100. } 101. else if (type instanceof GenericArrayType) 102. { 103. GenericArrayType t = (GenericArrayType) type; 104. System.out.print(""); 105. printType(t.getGenericComponentType()); 106. System.out.print("[]"); 107. } 108. 109. } 110. }
java.lang.Class<T> 1.0
TypeVariable[] getTypeParameters()
5.0
gets the generic type variables if this type was declared as a generic type, or an array of length 0 otherwise.
Type getGenericSuperclass()
5.0
gets the generic type of the superclass that was declared for this type, or null
if this type is Object
or not a class type.
Type[] getGenericInterfaces()
5.0
gets the generic types of the interfaces that were declared for this type, in declaration order, or an array of length 0 if this type doesn’t implement interfaces.
java.lang.reflect.Method 1.1
TypeVariable[] getTypeParameters()
5.0
gets the generic type variables if this method was declared as a generic method, or an array of length 0 otherwise.
Type getGenericReturnType()
5.0
gets the generic return type with which this method was declared.
Type[] getGenericParameterTypes()
5.0
gets the generic parameter types with which this method was declared. If the method has no parameters, an array of length 0 is returned.
String getName()
gets the name of this type variable.
Type[] getBounds()
gets the subclass bounds of this type variable, or an array of length 0 if the variable is unbounded.
Type[] getLowerBounds()
gets the subclass (extends
) bounds of this type variable, or an array of length 0 has no subclass bounds
Type[] getUpperBounds()
gets the superclass (super
) bounds of this type variable, or an array of length 0 has no superclass bounds.
Type getRawType()
gets the raw type of this parameterized type.
Type[] getActualTypeArguments()
gets the type parameters with which this parameterized type was declared.
Type getOwnerType()
gets the outer class type if this is an inner type, or null
if this is a top-level type.
Type getGenericComponentType()
gets the generic component type with which this array type was declared.
You have now reached the end of the first volume of Core Java. This volume covered the fundamentals of the Java programming language and the parts of the standard library that you need for most programming projects. We hope that you enjoyed your tour through the Java fundamentals and that you found useful information along the way. For advanced topics, such as networking, multithreading, security, and internationalization, please turn to the second volume.