12.8 类型推断和<>操作符
在调用泛型方法时,对于其中包含的形式类型参数,通常不需要显式指定其实际类型,这是因为编译器会根据方法调用时的上下文信息自动推断对应的实际类型。如果在泛型方法中使用了泛型类型声明中的形式类型参数,方法中的实际类型可以根据参数化类型的实际类型来推断。比如创建了一个类型为ArrayList<String>的对象,在调用该对象的add方法时,编译器自动推断出参数类型为String。编译器通过推断出的类型来拒绝不合法的方法调用。
如果泛型方法包含在非泛型类型中,或者方法声明中的形式类型参数与包含该方法的泛型类型的形式类型参数无关,那么可以通过两种方式来判断所使用的实际类型:一种方式是在方法调用时显式指定类型,另一种方式是由编译器根据方法调用上下文信息进行推断。代码清单12-27给出了一个非泛型类TypeInference中的泛型方法method的声明。
代码清单12-27 用来说明类型推断的非泛型类中的泛型方法
public class TypeInference{
public<T>T method(T obj){
return obj;
}
}
1.显式指定类型
在方法调用时可以显式指定类型。在代码清单12-28中,虽然方法调用时的参数的类型是String,但是通过显式的类型声明“<Serializable>”,方法调用时形式类型参数所表示的实际类型是Serializable,而不是String。
代码清单12-28 方法调用时显式指定类型的示例
TypeInference typeInference=new TypeInference();
typeInference.<Serializable>method("Hello");
在显式指定类型时,方法实际参数的静态类型不能与指定的类型发生冲突。在代码清单12-29中,在调用方法时显式指定了形式类型参数的类型为String,但是实际参数的静态类型为Object,无法通过自动类型转换与String类型兼容,因此代码出现编译错误。虽然变量str的运行时类型是String,但是编译器在处理时只考虑静态类型。
代码清单12-29 形式类型参数的指定类型与实际参数的静态类型发生冲突的示例
TypeInference typeInference=new TypeInference();
Object str="Hello";
typeInference.<String>method(str);//编译错误
2.类型推断
在大多数情况下并不需要显式地指定类型,编译器可以根据调用的上下文信息进行有效的推断。类型的自动推断有两种方式:第一种是根据方法调用时的实际参数的静态类型来进行推断,另一种是当方法调用的结果被赋值给另外一个变量时,可以根据该变量的静态类型进行推断,其中第一种方式的优先级较高。在代码清单12-30的方法调用中,实际的类型由调用时参数的静态类型来确定,为String类型。
代码清单12-30 基于实际参数的静态类型的类型推断的示例
TypeInference typeInference=new TypeInference();
typeInference.method("Hello");
在有些方法中,形式类型参数不出现在参数列表中,而是出现在返回值类型中。如果方法调用的结果被赋值给另外一个变量,则根据此变量的静态类型来推断实际类型。代码清单12-31中给出了TypeInference类中的另外一个泛型方法createList及其调用方式。在调用createList方法时,无法从参数推断出类型信息。由于方法调用的结果被赋值给一个List<Integer>类型的变量,因此实际的类型可以从该变量的类型进行推断,为Integer类型。
代码清单12-31 基于赋值操作的变量类型的类型推断的示例
public<T>List<T>createList(){
return new ArrayList<T>();
}
TypeInference typeInference=new TypeInference();
List<Integer>list=typeInference.createList();
在进行类型推断时只会考虑参数类型和接受赋值操作结果的变量类型这两种信息。代码清单12-32中给出了两种不同的调用方式。两种方式的作用都是把createList方法的调用结果作为参数传递给method方法。两种方式的区别在于最后得到的对象的类型不同。通过第一种方式调用createList方法时,编译器无法推断出实际使用的类型,因此只能使用Object作为实际类型;第二种方式通过一个临时变量来保存createList方法的调用结果,由于赋值操作的存在,编译器可以推断出实际类型为Integer。
代码清单12-32 通过临时变量来允许编译器进行类型推断的示例
List<Object>list1=typeInference.method(typeInference.createList());
List<Integer>list2=typeInference.createList();
List<Integer>list3=typeInference.method(list2);
3.<>操作符
Java SE 7把类型推断从方法调用扩展到了对象创建中,即增加了“<>操作符”(diamond operator)。在Java SE 7之前,创建一个泛型类型的对象总是需要显式指定实际的类型,在对象创建完成后,通常有一个类型兼容的对象引用指向它。一般的做法如代码清单12-33所示,泛型类型声明在构造方法的类型声明和创建的对象的引用类型声明中同时出现。这两者在很多情况下是相同的,产生了不必要的代码重复。
代码清单12-33 创建泛型类型的一般做法
List<String>list=new ArrayList<String>();
Map<List<?extends Number>,Map<String, Long>>map=new HashMap<List<?extends Number>,Map<String, Long>>();
Java SE 7对这个对象创建形式进行了简化,在调用构造方法时不再需要显式声明类型,直接使用“<>”来代替,具体的类型通过对象引用的类型来进行推断。这种做法与上面提到的调用方法时的类型推断机制在本质上是相同的。简化之后的创建方式如代码清单12-34所示。
代码清单12-34 使用<>操作符简化泛型类型的对象创建
List<String>list=new ArrayList<>();
Map<List<?extends Number>,Map<String, Long>>map=new HashMap<>();
创建对象可以看成是一种通过构造方法完成的特殊的方法调用形式,因此,之前提到的调用方法时的类型推断机制对构造方法也是适用的。在进行类型推断时,优先考虑调用构造方法的实际参数的静态类型,再考虑对象引用的静态类型。当可能出现错误时,显式指定类型来避免类型推断可能出现的问题。