第9章 泛型
泛型很大程度上是为了让集合能记住其元素的数据类型。
9.1 泛型入门
9.1.1 编译时不检查类型的异常
ch9; java.util.ArrayList; java.util.List; { main([] args) { List strList ArrayList(); strList.add(); strList.add(); strList.add(); strList.forEach(strSystem.out.println((()str).length())); } }
当程序“不小心”把一个Integer对象丢进List集合,导致强制类型转换成String时引发ClassCastException异常。
9.1.2 使用泛型
java5 后引入了“参数化类型”(parameterized type)的概念,允许程序在创建集合时指定集合元素的类型。如List<String>
,表明该List只能存放String类型对象。
Java的参数化类型被称为泛型(Generic)。
对于前面的ListErr程序,可以使用泛型改进。
List strList ArrayList();
9.1.3 Java7泛型的“菱形”语法
java7 之前使用泛型的接口、变量时,构造器也必须带泛型:
List<String> strList = new ArrayList<String>();
Java7之后,允许省略构造器后面的泛型信息,只给出<>即可:
List<String> strList = new ArrayList<>();
9.2 深入泛型
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。Java5改写了集合中的全部类和接口,提供了泛型支持。
9.2.1 定义泛型接口、类
可以为任何类、接口增加泛型声明。下面自定义一个Apple类:
ch9; T { T info; Apple() {} Apple(T info) { .info info; } T getInfo() { info; } setInfo(T info) { .info info; } main([] args) { Applea1 Apple(); System.out.println(a1.getInfo()); Applea2 Apple(); System.out.println(a2.getInfo()); } }
9.2.2 从泛型类派生子类
从泛型类、接口派生时,父类不能再包含类型形参:
AppleT{}
如果想从Apple派生出一个子类,可以使用如方式:
public class A extends Apple<String>
也可以是:
public class A extends Apple
定义类、接口、方法时可以声明类型形参,使用时应该向类型形参传入实际类型。
如果从Apple<String>
派生子类,Apple类中的T都会被替换成String。
如果使用Apple类时没有传入实际类型参数,java编译器可能会发出警告:使用未经检查或不安全的操作。此时,系统会把Apple<T>
的T当成Object类型处理。
9.2.3 并不存在泛型类
不管为泛型的类型形参传入哪一种形参,对于Java来说,它们仍然被当成同一个类处理,在内存中也只占用一块内存空间。 因此,静态方法、静态变量、静态初始化块中不允许使用类型形参。
由于系统并没有真正生成泛型类,所以instanceof也不能用于泛型类。
9.3 类型通配符
9.3.1 使用类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号?,这个问号表示可以匹配任何类型:
(List c){ ( i ; i c.size(); i){ System.out.println(c.get(i)); } }
可以使用任何类型的List来调用它。
但这种List<?>仅表示它是各种List的父类,并不能把元素加入其中,
List c ArrayList(); c.add( ());
因为无法确定c的类型,所以不能向其中添加对象。
9.3.2 设定类型通配符的上限
如果程序希望List<?> 只是某一类泛型List的父类:
List Shape
类似地,由于无法确定这个受限制通配符的具体类型,所以不能添加对象进这个集合。
9.3.3 设定类型形参的上限
表示传入的类型形参是上限类型或是上限类型的子类:
T { T col; ... }
另一个更严格地情况是,类型形参有多个上限:(使用&符号连接多个上限)
T必须是Number或Number子类,并且必须实现ava.io.Serializable接口 T java.io.Serializable
9.4 泛型方法
9.4.1 定义泛型方法
所谓泛型方法,就是声明方法时定义一个或多个类型形参:
修饰符 T,S 返回值类型 (形参列表) { }
ch9; java.util.ArrayList; java.util.Collection; { T fromArrayToCollection(T[] a,CollectionT c) { (T o: a) { c.add(o); } } main([] args) { [] oa []; Collection co ArrayList(); fromArrayToCollection(oa, co); [] sa []; Collection cs ArrayList(); fromArrayToCollection(sa, cs); fromArrayToCollection(sa, co); } }
方法中的泛型形参无需显式传入实际类型参数,调用时根据实参推断类型形参的值。
9.4.2 泛型方法和类型通配符的区别
大多数时候都可以使用泛型方法来代替类型通配符
E { containsAll(Collection c); addAll(Collection E c); }
E { T containsAll(CollectionT c); T E addAll(CollectionT c); }
类型通配符:类型形参T的唯一效果是可以在不同调用点传入不同的实际类型。
泛型方法允许类型形参表示方法的一个或多个参数之间的类型依赖关系,或是方法返回值与参数之间的类型依赖关系。如果没有这种关系,就不该用泛型方法。
如果需要,也可以同时使用泛型方法和通配符:
T (ListTdest, List exteds Tsrc)
9.4.3 Java 7 的菱形语法与泛型构造器
构造器签名中也可以声明类型形参。
如果构指定了造器中声明的类型形参的实际类型,则不可以使用“菱形”语法。
9.4.4 设定通配符下限
<? super Type>
表示是Type或Type父类
9.4.5 泛型方法与方法重载
T (CollectionTdest, Collection exteds Tsrc) T T (Collection superTdest, Collection Tsrc)
调用copy时会引起编译错误。
9.4.6 Java 8 改进的类型推断
1、根据调用方法的上下文推断参数的目标类型
2、在方法调用链中,将推断得到的类型参数传递到最后一个方法。
需要注意的是,这种推断并不万能,有些地方要手动指定类型参数。
9.5 擦除和转换
在严格地泛型代码中,带泛型声明的类总应该带着类型参数。但为了与老版本兼容,也允许不指定类型参数。如果没指定类型参数,则该类型参数为raw type(原始类型),默认为声明该参数指定的第一个上限类型。
9.6 泛型和数组
数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。
简单来说,不要创建泛型数组。
9.7 小结
奇怪的知识增加了。
很多奇怪的用法,奇怪的错误,比如9.5和9.6节。