Java中的泛型
一、引入泛型程序的背景
在Java泛型类出现之前,程序员必须使用Object编写适用于多种类型的代码。这很繁琐,也很不安全。随着JDK5泛型的引入,编写代码可以对多种不同类型的对象重用。
二、泛型语法
泛型类(generic class)就是有一个或多个类型变量的类。
1.定义泛型类
下面这个泛型类引入了一个类型变量T(Type),用“<>”括起来,放在类名的后面,“<>”表示这里面的所有类型变量在泛型类被实例化时,需要初始化其类型。泛型类还可以有很多个类型变量,均写在“<>”里,用“,”隔开,如ClassName<T, U>。类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型,如下面的field就是一个该类的T数据类型的属性字段。
public class ClassName<T>{
private T field;
}
2.定义泛型方法
泛型方法不仅仅只能在泛型类中定义,也可以在普通类中定义。现在将上面定义的泛型类进行扩充,添加构造方法和其他方法。
- 当方法返回的是T类型的值时,类型变量“< T >”应放在修饰符“public static”后,返回类型“T”前。
public class ClassName<T>{
private T field;
public ClassName(T field){
this.field = field;
}
public T method1(){
//do same work
return parameter;
}
public static <T> T method2(){
//do same work
return parameter;
}
}
- 在调用泛型方法时,可以把具体类型包裹在“<>”中,放在方法名前面。几乎在所有情况下,“<>”可以省略,因为编译器能够推断出T的类型。但是,当返回类型存在多种情况下,编译器将会报错。
String str = Class.<String>method2(); //未省略<String>
String str = Class.method2(); //省略<String>
3.类型变量的限定
当类型变量T的实例需要调用某一个方法时,必须限制T的类型,即将T类型继承某个类或接口,这样就能调用该类或接口里的方法。类型变量限定的语法如下,多个限定用“&”分隔。其中多个限定只能有一个是类,其它为接口,且类必须放在第一位。这与Java中类的继承类似,因为类只能继承一个超类,但却能实现多个接口。为保持类型安全性,必要时虚拟机会插入强制类型转换。
<T extends BoundingType1 & BoundingType2 & BoundingType3>
4.虚拟机如何运行泛型代码
虚拟机中没有泛型的概念,只有普通的类和方法。所以在执行代码时,会将所有类型参数替换为它们的限定类型(没有限定类型的将用Object替换)或叫原始类型(raw type),这被称作类型擦除。为避免替换类型变量后,继承类方法的重写出现问题,编译器会在继承的类中合成一个桥方法(bridge method),使该桥方法变为重写方法,原重写方法变为重载方法。
/**
*程序员写的代码
*/
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
class DateInterval extends Pair<LocalDate>{
public void setSecond(LocalDate second){
if(second.compareTo(getFirst())>=0){
super.setSecond(second);
}
}
}
/**
*虚拟机将 T类型替换成限定类型(Object)后的代码
*/
public class Pair {
private Object first;
private Object second;
public Pair() {
first = null;
second = null;
}
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
class DateInterval extends Pair{
//原重写的方法在这里变为重载方法
public void setSecond(LocalDate second){
//getFirst()类型也会被虚拟机进行强制转换
if(second.compareTo((LocalDate)getFirst())>=0){
super.setSecond(second);
}
}
//虚拟机合成的桥方法(此方法变为重写方法)
public void setSecond(Object second){
setSecond((LocalDate)second);
}
}
三、限制与局限性
1.不能用基本数据类型实例化类型参数
Java的8种基本数据类型不能代替类型参数。因为基本数据类型不存在限定类型,因此虚拟机没法进行类型擦除,就无法运行该代码。
2.运行时类型查询只适用于原始类型
虚拟机在运行时,会擦除T类型变量,因此查询到的类均为原始类型,这些查询包括instanceof()、getClass()等方法。
3.不能创建参数化类型的数组
虚拟机在运行时,会擦除T类型变量,故只能创建普通类的数组。
Class1<Class2>[] class1; //合法
new Class1<Class2>[10]; //不合法
4.不能实例化类型变量
虚拟机在运行时,会擦除T类型变量,故T会被视为其原始类型。
T parameter; //合法
new T(); //不合法
5.不能构造泛型数组
同上,虚拟机在运行时,会擦除T类型变量,故T会被视为其原始类型。
T[] array; //合法
new T()[10]; //不合法
6.泛型类的静态上下文中类型变量无效
不能在静态字段或方法中引用类型变量。因为在创建对象时,如果声明了两个不同T(如String和Integer)的该类时,该静态字段无法共享,因为它只能是具体的某个数据类型。静态方法也是如此。因此,禁止使用带有类型变量的静态字段和方法。
public class ClassName<T>{
private static T field; //不合法
//合法
public static <T> T method(){
//do same work
return parameter;
}
//不合法
public static T method(){
//do same work
return parameter;
}
}
7.不能抛出或捕获泛型类的实例
不要用泛型类继承Throwable,因为catch子句中不能使用类型变量。
461

被折叠的 条评论
为什么被折叠?



