目录
为什么要用泛型程序设计
-
泛型程序设计意味着编写的代码可以对多种不同类型的对象重用。
例如,你并不想为收集String和File对象编写不同的类。实际上也不需要这样做,因为一个ArrayList类就能收集任何类的对象。这就是泛型程序设计的一个例子。
类型参数的好处
-
泛型提供了一个更好地解决方案,类型参数。ArrayList类有一个类型参数用来指示元素的类型。
Var a=new ArrayList<String>();
-
这使得代码具有更好地可读性。人们一看就知道整个数组列表中包含的是String对象。
关于泛型程序设计
-
使用ArrayList的泛型类很容易,大多数Java程序员都会使用类似于ArrayList<String>这样的类型。但是实现一个泛型类并不容易。使用你的代码的程序员可能会插入各种各样的类作为类型参数。他们希望一切都运行正常,不会出现恼人的限制,或者其他错误消息。因此,作为一个泛型程序员,我们的任务就是要预计到你的泛型类所有可能的用法。
-
这当然是一个艰难的问题,ArrayList有一个方法addAll用来添加另一个集合的所有元素。一个程序员可能会想将一个ArrayList<Manger>中的所有元素添加到一个ArrayList<Employee>中去。但是反过来就不能调用了,未来解决这个问题,Java语言的设计者发明了一个具有独创性的新概念来解决这个问题,即通配符类型。
定义简单的泛型类
-
泛型类就是有一个或多个类型变量的类
-
泛型类的定义格式:修饰符 class 类名 <类型>{} 图30
-
泛型类可以有多个类型变量。例如定义一个Student类,其中的第一个和第二个字段使用不同的类型
-
public class Student<T,U>{....}
-
可以使用泛型类将对象的泛型设置为不同的类型
泛型方法
-
泛型方法可以在普通类或者泛型类中定义
-
泛型方法的定义:类型变量放在修饰符的后面,并在返回类型的前面
-
泛型方法的定义格式:修饰符<类型>返回值类型 方法名(类型,变量名){}
-
在主程序中调用这个方法的时候可以传输任意类型的参数
泛型接口
-
泛型接口格式:修饰符 interface 接口名 <类型>{}
-
泛型接口和它的实现类:
-
实例化实现类
类型变量的限定
-
有时我们需要对类型变量加以约束,请看下面这个案例:
public static <T> T min(T[] a){ ... T smallest=a[0]; }
变量smallest的类型为T,这意味着它能使人格一个类的对象,如何知道T所属的类有一个compareTo方法呢?
-
解决这个问题的办法只能是限制T实现了Comparable接口可以通过对类型变量T的限定来实现这一点
public static<T extends Comparable>T min(T[] a)...
-
在这里我们使用extends而不是implements,在这里表示T应该是限定类型Comparable的子类型。T和限定类型可以是类也可以是接口,选择关键字extends的原因是它更接近子类型的概念
-
一个类型变量或者通配符可以有多个限定,例如:
<T extends Comparable&Serializable>
-
限定类型用“&”分隔开,而逗号用来分隔类型变量
-
在Java的继承中,可以根据需要拥有多个接口超类型,但最多有一个限定是类。如果有一个类作为限定,它必须是限定列表的第一个限定
-
类型通配符
-
为了表示各种泛型List的父类,可以使用类型通配符
-
类型通配符:<?>
-
List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
-
这种带类型通配符的List仅代表的是各种泛型List的父类,并不能将元素添加到其中
-
-
如果我们希不希望List<?>是任何泛型List的父类只希望它是某一类泛型List的父类,可以使用类型通配符的上限
-
类型通配符上限:<? extends 类型>
-
<List? extends Number>:它表示的类型是Number或者其子类型
-
-
除了指定通配符的上限我们还能指定通配符的下限
-
类型通配符下限:<? super 类型>
-
<List? super Number>:它表示的类型是Number或者其父类型
-
图36
关于泛型程序设计的问题
-
关于泛型代码的类型擦除问题
不管什么时候定义一个类,都会提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型
public class pair<T>{ private T first; public Pair(T first,T second) }
-
在这里Jvm就会将T替换为Object,因为T是一个无限定的变量,所以直接用Object替换
-
当我们声明了一个稍有不同的变量时
<T extends Comparable&Serizable>//被限定为Comparable <T extends Serizable&Comparable>//被限定为Serizable
-
-
转换泛型表达式问题
-
在泛型方法调用时,若擦除了返回类型,编译器会插入强制类型转换
Pair<Employee>bud=...; Employee buddy=bud.getFirst();
-
在这里getFirst擦除类型后返回的是Object。编译器自动的插入转换到Employee的强制类型转换。也就是说编译器执行了两条虚拟指令:
-
对原始Pair.getFirst方法的调用
-
将返回的Object类型强制转换为Employee类型
-
-
当然在访问一个泛型字段的时候也要强制插入强制类型转化
-
-
转换泛型方法问题
链接
-
对于Java泛型的转换,需要记住以下几个事实:
-
虚拟机中没有泛型,只有普通的类和方法
-
所有的类型参数都会替换为它们的限定类型
-
会合成桥的方法来保持多态
-
为了保持类型安全性,必要时会插入强制类型转换
-
关于泛型程序设计的限制和局限性
-
不能用基本类型实例化参数类型
因此没有Pair<double>只有Pair<Double>。当然其原因就在于类型擦除。擦除之后Pair类含有的Object类型的字段,而Object字段不能存储double值。
-
运行时类型查询只适用于原始类型
虚拟机的对象总有一个特定的非泛型类型,因此所有的原类型查询只产生原始类型。
Pair<String>stringpair=...; Pair<Employee>employeePair=...; if(Stringpair.getClass()==employeePair.getClass)//结果返回true
这是因为两次的getClass返回的值都是Pair.Class
-
不能创建参数化类型的数组
-
不能实例化类型变量
不能再类似new T()的表达式中使用类型变量,例如
public Pair(){first=new T();second=new T();}
类型擦除之后将T变为了Object,你肯定不希望出现new Object();
-
不能构造泛型数组
链接:不能构造泛型数组
-
泛型类的静态上下文中的类型变量无效 不能在静态字段或者方法中引用类型变量
-
不能捕获或者抛出泛型类的实例
即不能抛出或者捕获泛型类的对象。实际上泛型类扩展Throwable甚至都是不合法的,例如以下就不能编译
public class Prom<T> extends Exception//Error,cannot extend Throwable
catch子句不能使用类型变量,例如以下就不能编译
try{do work}catch(T t){...}
不过在异常规范中使用类型变量时允许的,以下方法时是合法的
publci static <T extends Throwable>void d{ try{ do work; }catch(Throwable realCause){ ... } }
-
可以取消对检查型异常的检查
-
注意类型擦除后的冲突
链接:类型擦除
参考书籍:Java核心技术 卷1(原书第11版)