一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类.如果要编写可以应用与多种类型的代码,这种刻板的限制对代码的束缚就会很大.
在面向对象的编程语言中,多态算是一种泛化机制.例如方法中接收object类型作为参数,便可以接收任意类型的参数.
15.1 与c++的比较
理解java泛型的局限是什么,为什么会有这些局限,理解了边界所在,你才能成为程序高手.15.2 简单泛型
有很多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类.事实上,所有的程序,在运行时都要求你持有一大堆对象,所以,容器算是最具有重用性的类库之一.
15.2.1 一个元组类库
仅一个方法调用就能返回多个对象,你应该经常需要这样的功能.可是return语句只允许返回单个对象,因此解决的办法是用它来创建一个对象,用它来持有想要返回的多个对象.这个概念称为"元组",他是一组对象直接打包存储于其中的一个单一对象.注意:元组中一般保持了其中元素的次序.
15.2.2 一个堆栈类
自己实现Nebuchadnezzar链式存储机制hasNext
15.3 泛型接口
泛型也可以运用于接口.interface Inner<T>{
T getT();
}
class InnerImpl implements Inner<Integer>{
@Override
public Integer getT() {
return null;
}
}
15.4 泛型方法
class Impl<W>{
public void get(W w ) { // 类泛型
}
public static<G> void getG(G g){ // 静态方法泛型
}
public <T>void T (T t) { // 普通方法的独立泛型
}
}
泛型方法需要在方法上声明泛型<T>,而静态方法不能使用类的泛型,因为类的泛型是属于对象,而静态成员先于对象而存在.
15.4.1 可变参数和泛型方法
public static <G> void getG(G ... ggs){}
15.5 泛型用于匿名内部类
15.6 构建复杂模型
使用泛型可以很简单的构建一个自己需要一个复杂的数据类型.List<?>15.7 擦除的神秘之处
Class<? extends ArrayList> arrayClass = new ArrayList<String>().getClass();
Class<? extends ArrayList> integerClass = new ArrayList<Integer>().getClass();
System.out.println(arrayClass == integerClass); // true
上述泛型integer和string获得的Class类型为一样
List<Integer> list = new ArrayList<>();
TypeVariable<? extends Class<? extends List>>[] types = list.getClass().getTypeParameters();
System.out.println(Arrays.toString(types)); // E
并没有获取到Integer而是获取到了E
输出是的标识符,并非有用的信息,因此,在泛型代码内部,无法获得任何有关泛型参数类型的信息.
你可以直到诸如类型参数标识符和泛型边界这类的信息,你确无法直到用来创建某个特定实例的实际的类型参数.
java的泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都会被擦除,你唯一直到的就是你在使用一个对象.因此List<Integer> 和List<String>在运行时其实是相同的类型.者两种形式都被擦除成它们的"原生"类型,即List.
15.7.1 c++的方式
java不能使用t对象进行任何原本的操作.15.7.2 迁移兼容性
如果泛型在java1.0中就已经是其一部分了,那么这个特性将不会使用擦除来实现----它将使用具体化,使类型参数保持为第一类实体,因此你就能够在类型参数伤执行基于类型的语言操作和发射操作.在基于擦除的实现中,泛型类型被当作第二类型处理,即不能在某些重要的上下文环境中使用的类型.泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界.例如,List<T>这样的类型注解将被擦除为List,而普通的类型变量在未被指定边界的情况下将被擦除为object.
擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为"迁移兼容性".
因此java泛型不仅必须支持向后兼容性,即现有的代码和类文件依旧合法,并且继续保持之前的含义;而且还要支持迁移兼容性,使得类库按照它们自己的步调变为泛型的,并且当某个类型变为泛型时,不会破坏依赖于它的代码和应用程序.在决定了这就是目标之后,java设计者们和从事此问题相关工作的各个团队决策认为擦差是唯一可行的解决方案.
15.7.3 擦除的问题
擦除的动机是崇高的,但是代价是显著的,泛型不能用于显示地一用运行时类型的操作之中,例如转型,instanceof操作和new表达式.因为所有关于参数类型信息都丢失了,无论何时,当你在编写泛型代码时,必须时刻提醒自己,"它是一个object"15.8 擦除的补偿
使用 isInstance来进行补偿class A {}
class B extends A{}
class C<T>{
Class<T> ct;
public C(Class<T> ct) {
this.ct = ct;
}
public boolean ff(Object object){
System.out.println(ct);
return ct.isInstance(object);
}
public static void main(String[] args) throws Exception {
C<B> ca = new C<B>(B.class);
System.out.println(ca.ff(new B())); // true
System.out.println(ca.ff(new A())); // false
}
}
15.8.1 创建类型实例
java里中使用工厂方法,接受一个Class对象来创建class A{}
class Factory{
public<T> T getT(Class<T> tClass){
try {
return tClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
Factory ff = new Factory();
A t = ff.getT(A.class);
System.out.println(t);
}
}
另一种方式就是使用模版方法设计模式(设计模式详解)
15.8.2 泛型数组
不能创建泛型数组,一般的解决方案是在任何需要创建泛型数组的地方都是用ArrayList因为有了擦除,数组的运行时类型就智能是Object[].
15.9 边界
详见14章中的边界代码15.10 通配符
impot java.util.*;<? extends A>
15.10.1
逆变使用超类型通配符
<? spuer A>
15.10.2 无边界通配符
List<?>Map<String,?>
15.11 问题
任何基本数据类型都不能作为类型参数.使用包装类型参数.java5中包含了自动装箱和自动拆箱
15.11.2 实现参数化接口
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会称为相同的接口.interface A<T>{
void aa();
}
class B implements A<B>{
@Override
public void aa() {}
}
class C extends B implements A<C>{ // 编译不通过
}
去掉泛型可以编译通过
15.11.3 重载
class B <K,V>{
void get(K k){}
void get(V v){}
}
15.11.4 基类劫持了接口class A implements Comparable<A>{
@Override
public int compareTo(A o) {
return 0;
}
}
class B extends A implements Comparable<B>{}
B类编译不能通过
class A implements Comparable<A>{
@Override
public int compareTo(A o) {
return 0;
}
}
class B extends A implements Comparable<B>{}
15.12 自限定类型
class A<T extends A<T>>{}
15.12.1 古怪的循环类型
简单版本class A<T>{}
class B extends A<B>{}
为了理解其含义,努力大声说:"我在创建一个类,它继承自一个泛型类型,这个泛型类型接收"
class A<T extends A<T>> {
private T t;
public void getT() {
System.out.println(t.getClass());
}
public void setT(T t) {
this.t = t;
}
}
class B extends A<B> {}
它的最终版本,和用途在于,如果我们需要一个以自己类型为参数化类型的类,我们可以继承一个这样的基类.
15.12.3 参数协变
自限定类型的价值在于它们可以产生协变参数类型---方法参数类型会随着子类而变化.协变参数类型在java5中引入.
15.13 动态类型安全
Collections 中的静态方法:checkedCollection(),checkedList(),checkedMap().checkedSet,checkedSortedMap(),checkedSortedSet().接收第一个参数为容器,第二个参数是你希望强制要求的类型.
15.14 异常
由于擦除的原因,将泛型应用于异常是非常受限的.
<T extends Exception>(参考)15.15 混型
术语混型随事件的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以标识混型中所有类型的类.15.15.2 与接口的混合
15.15.3 使用装饰者设计模式
(详见设计模式)15.15.4 动态代理(详见设计模式)
15.16 潜在类型机制
其他语言 Python 和C++因为泛型是在这场竞赛的后期才添加到java中的,因为java没有任何机会可以去实现任何类型的潜在类型机制,因此java没有对这种特性的支持.
15.17 对缺乏潜在类型机制的补偿
- 反射
- 边界+通配符+协变参数+可变参数和泛型方法
public static <T,S extends Iterable<? extends T>> void get(Method method, S ss, Object ... obj) {
for (T t: ss){
try {
method.invoke(t);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
首先S的类型为Iterable是一个集合
而集合中的元素的类型为? 它的上边界为T
尽管java解决方案被证明很优雅,但是我们必须直到使用反射可能比非反射的实现要慢一些,因为有太多的动作都是在运行时发生的.这不应该阻止你使用这种解决方案的脚步,至少可以将其作为一种马上就能想到的解决方案.