Generic type parameters are commonly defined at the class or interface level, but methods and (rarely) constructors also support declaring type parameters bound to the scope of a single method call.
class Utility // no generics at the class level
{
@SafeVarargs
public static <T> T randomOf(T first, T... rest) {
int choice = new java.util.Random().nextInt(rest.length + 1);
return choice == rest.length ? first : rest[choice];
}
public static <T extends Comparable<T>> T max(T t1, T t2) {
return t1.compareTo(t2) < 0 ? t2 : t1;
}
}
Notice the type parameter declarations, T
and <T extends Comparable<T>>
respectively, appear after the method modifiers and before the return type. This allows the type parameter T
to be used within the scope of such methods, acting as:
Though both methods above use the same type parameter name T
, at the method level they are completely independent of each other. The compiler will infer the actual type based on the arguments passed to the method at each call site that invokes the method. Since the max
method declares that T extends Comparable<T>
, the compiler also enforces that the inferred types are compatible implementations of the Comparable
interface.
Integer num1 = 1;
Integer num2 = 2;
String str1 = "abc";
String str2 = "xyz";
Integer bigger = Utility.max(num1, num2);
assert bigger == num2;
String later = Utility.max(str2, str1);
assert later == str2;
Utility.max(num1, str1); // compiler error: num1 and str1 are incompatible types
Utility.max(new Object(), new Object()); // compiler error: Object does not implement Comparable
Java 8 significantly improved the compiler's ability to correctly infer the generic types at call sites. If the compiler fails to infer the proper type, developers can explicitly state the type as a part of the call:
Object obj = Utility.<Object>randomOf(str1, new Object(), num1);