Jakob Jenkov 2024-07-06
Java 泛型不仅可以用于类,也可以用于方法。换句话说,你可以对 Java 方法进行泛型化。更具体地说,你可以在 Java 方法的参数和返回类型中指定泛型类型。
以下是一个 Java 泛型方法的示例:
public static <T> T addAndReturn(T element, Collection<T> collection){
collection.add(element);
return element;
}
这个 Java 方法声明了一个泛型类型 T,它既用作 element 参数的类型,也用作 Collection 的泛型类型。注意,现在可以向集合中添加元素了。如果在 Collection 参数定义中使用 Java 泛型通配符(wildcard),这是不可能做到的。
那么,Java 编译器是如何知道 T 的类型的呢?
答案是:Java 编译器会根据你对该方法的调用来推断出类型。
例如:
String stringElement = "stringElement";
List<String> stringList = new ArrayList<String>();
String theElement = addAndReturn(stringElement, stringList);
在这个例子中,Java 编译器会推断出类型 T 为 String,因为传给 addAndReturn() 方法的第一个参数的类型是 String,同时传入的集合的泛型类型也是 String。
我们也可以在其他类型上直接使用 addAndReturn(),无需做任何修改。
下面的例子展示了如何将 addAndReturn() 方法与一个 Integer 对象以及泛型类型为 Integer 的 List 一起使用:
Integer integerElement = new Integer(123);
List<Integer> integerList = new ArrayList<Integer>();
Integer theElement = addAndReturn(integerElement, integerList);
和前面的例子一样,Java 编译器会推断出 addAndReturn() 的泛型类型为 Integer,因为传入的第一个参数的类型是 Integer,而且作为第二个参数传入的 List 的泛型类型也是 Integer。
如你所见,完全相同的方法可以以类型安全的方式用于不同的类型!Java 编译器会根据你在代码中如何使用该方法(包括返回类型和参数类型)来推断出该方法应处理的具体类型。
高级类型推断
Java 编译器甚至可以执行更高级的类型推断。例如,下面的调用也是合法的:
String stringElement = "stringElement";
List<Object> objectList = new ArrayList<Object>();
Object theElement = addAndReturn(stringElement, objectList);
在这个例子中,我们对 T 使用了两种不同的类型:String 和 Object。编译器会选择使方法调用类型正确的最具体的公共类型。
注意,使方法调用类型正确的“最具体类型”并不一定是最具体的使用类型。在上面的例子中,最具体的使用类型是 String,但如果编译器将 T 推断为 String,那么将 List<Object> 作为第二个参数传递给 addAndReturn() 就会导致编译错误。因此,编译器将 T 推断为 Object,因为 Object 是在此情况下对两个参数都有效的最具体类型。
然而,反过来的泛型类型组合是不合法的:
Object objectElement = new Object();
List<String> stringList = new ArrayList<String>();
Object theElement = addAndReturn(objectElement, stringList);
在这种情况下,Java 编译器推断出:为了使方法调用类型安全,泛型类型 T 必须是 String。那么传入 T element 参数的 objectElement 也必须是 String 类型(但它不是)。因此,编译器会报错。
将 Class 对象作为类型字面量
假设你有一个方法接收一个 Class 对象,并且需要返回该类的一个实例。这时,你希望 Java 编译器能根据传入的 Class 对象推断出方法的返回类型。这在 Java 中实际上是可行的。
请看下面的例子:
public static <T> T getInstance(Class<T> theClass) throws IllegalAccessException, InstantiationException {
return theClass.newInstance();
}
注意,该方法在 Class 参数和方法返回类型上都声明了泛型类型 T。这向 Java 编译器表明:方法的返回类型应该与传入的 Class 对象的类型一致。因此,如果你传入 String.class,那么 T 就会被推断为 String。
我在教程《Java 泛型 - 将 Class 对象作为类型字面量》中对此有更详细的解释。