Chapter 13. Generic Programming

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.

Why Generic Programming?

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.

Who Wants to Be a Generic Programmer?

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.

Definition of a Simple Generic Class

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

Note

Note

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.

C++ Note

C++ Note

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. }

Generic Methods

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);

C++ Note

C++ Note

In C++, you place the type parameters after the method name. That can lead to nasty parsing ambiguities. For example, g(f<a,b>(c)) can mean “call g with the result of f<a,b>(c)”, or “call g with the two Boolean values f<a and b>(c)”.

Bounds for Type Variables

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.

C++ Note

C++ Note

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. }

Generic Code and the Virtual Machine

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.

Note

Note

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.

C++ Note

C++ Note

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;
}

Note

Note

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.

Translating Generic Expressions

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.

Translating Generic Methods

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.

Note

Note

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.

Calling Legacy Code

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

Note

Note

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.

Restrictions and Limitations

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.

Primitive Types

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.

Runtime Type Inquiry

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.

Exceptions

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;   }}

Arrays

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.

Tip

Tip

If you need to collect parameterized type objects, simply use an ArrayList: ArrayList<Pair<String>> is safe and effective.

Instantiation of Generic Types

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.

Static Contexts

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.

Clashes after Erasure

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> { . . . 
Clashes after Erasure} // 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.

Inheritance Rules for Generic Types

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).

No inheritance relationship between pair classes

Figure 13–1. No inheritance relationship between pair classes

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.

Subtype relationships among generic list types

Figure 13–2. Subtype relationships among generic list types

Wildcard Types

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).

Subtype relationships with wildcards

Figure 13–3. Subtype relationships with wildcards

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.

Supertype Bounds for Wildcards

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);
}
A wildcard with a supertype bound

Figure 13–4. A wildcard with a supertype bound

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.

Unbounded Wildcards

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.

Wildcard Capture

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. }

Reflection and Generics

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.

Reflection and Genericsreflectiongenerics andgenericsreflection and
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.

Reflection and Genericsreflectiongenerics andgenericsreflection and
java.lang.reflect.Constructor<T> 1.1
  • T newInstance(Object... parameters) 5.0

    returns a new instance constructed with the given parameters.

Using Class<T> Parameters for Type Matching

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, 
Using Class<T> Parameters for Type MatchingextendinginheritanceparametersClass(T)typestype matching and Class(T)matching types and Class(T)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>.

Generic Type Information in the Virtual Machine

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.

Note

Note

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.

The Type class and its descendants

Figure 13–5. The Type class and its descendants

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
The Type class and its descendants(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. }
GenericReflectionTest.java
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.

GenericReflectionTest.java
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.

GenericReflectionTest.java
java.lang.reflect.TypeVariable 5.0
  • 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.

GenericReflectionTest.java
java.lang.reflect.WildcardType> 5.0
  • 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.

GenericReflectionTest.java
java.lang.reflect.ParameterizedType 5.0
  • 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.

GenericReflectionTest.java
java.lang.reflect.GenericArrayType 5.0
  • 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset