泛型
这是《Java8编程参考官方教程》第14章的笔记。
泛型(Generic):参数化类型。使用泛型的类、接口、方法,可以作为参数指定所操作数据的类型。
使用泛型,所有的类型转换都是自动和隐式进行的。
通过操作Object类型的引用,Java可以创建一般化的类、接口以及方法。Object是所有类的超类。
起始自JDK 5。
当声明泛型类的实例时,传递过来的类型参数必须是引用类型,不能使用基本类型。
使用泛型相较于直接简单地将Object作为数据类型、作为参数的优势:能够在编译时捕获类型不匹配错误,本质上,也就是可以将运行时错误转换成编译时错误。类型安全,这是泛型的主要优势。
有界类型(bounded type):控制类型参数的类型范围。实现强制类型安全。
<T extends superclass>;
<T extends interface>;
<T extends interface & interface &...>;
<T extends MyClass & MyInterface>; 此时,所有传递给T的类型参数都必须是MyClass的子类,并且必须实现MyInterface接口。
p.s.这里的 T 只是一个占位符。
通配符参数:“?”,表示未知类型。
有界通配符:<? extends superclass>,这是一条包含语句,意思是superclass也位于边界之内。
<? super superclass>
泛型方法:可以在非泛型类中创建泛型方法。
<type-param-list > return-type method-name (param-list) { //...
类型参数在方法的返回类型之前声明。
还可以声明泛型构造方法。
泛型接口:
- 如果实现泛型接口的类没有声明类型参数,编译器会报告错误;
- 如果类实现了某种具体类型的泛型接口,那么实现类不需要是泛型化的。
interface interface-name<type-param-list> { //...
class class-name<type-param=list> implements interface-name<type-arg-list> { //...
原始类型(raw type)与遗留代码:为了处理泛型过渡,Java允许使用泛型类而不提供任何类型参数。
泛型类层次:当一个泛型类继承另一个泛型类时,它的类型参数必须与超类的类型参数类似,不过子类可以自由添加自己的类型参数。
泛型类可以继承一个非泛型类作为自己的超类。
请记住:在运行时不能使用泛型类型信息。所以:类似instanceof(Gen<Interger>)不能通过编译,要写成instanceof(Gen<?>)。
只有当两个泛型类实例的类型相互兼容并且他们的类型参数也相同时,才能将其中的一个实例强制转换为另一个实例。
重写泛型类的方法:与非泛型类相似。
泛型的类型推断:从JDK 7开始,e.g.
MyClass<integer, String> mcOb = new MyClass<Integer, String>(98, "A String"); 可以写成MyClass<integer, String> mcOb = new MyClass<>(98, "A String"); Java会自己推断构造函数所需要的类型参数。在向方法传递参数时也可以这样用。
这样做是为了缩短有时相当长的声明语句。
擦除:编译Java代码时,所有泛型信息被移除(擦除)。
使用这种方式实现泛型,
- 可以不修改JVM而向前兼容;
- 意味着在运行时没有类型参数,它们只是一种源代码机制。
桥接方法(bridge method):发生在字节码级别,看不到,不能用。
模糊性错误:e.g. MyClass<T, V>意思是T、V是两个不同类型的参数,但是如果在实例化的时候写成了两个相同类型的参数,就可能会发生模糊性错误,e.g. set(T o)和set(V o)变成了两个相同的方法。
应该使用两个独立的方法名,或别的做法来避免这种错误。
一般来说,模糊性通常意味着在设计中存在概念性错误。
使用泛型的一些限制:
- 不能实例化类型参数的实例。T 只是一个占位符。
- 静态成员不能使用在类中声明的类型参数,但是可以声明静态的泛型方法。
- 不能实例化元素类型为类型参数的数组;不能创建特定类型的泛型引用数组,如果要用,使用通配符 ? 。
- 泛型类不能扩展Throwable,这意味着不能创建泛型异常类。