什么是泛型?为什么要使用泛型?
泛型就是参数化类型
优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。
泛型主要使用在集合中
List arrayList = new ArrayList(); arrayList.add("aaaa"); arrayList.add(100); for(int i = 0; i< arrayList.size();i++){ String item = (String)arrayList.get(i); Log.d("泛型测试","item = " + item); }
毫无疑问,程序的运行结果会以崩溃结束:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
当在赋值的时候,上面一节说赋值的都是为具体类型,当赋值的类型不确定的时候,我们用通配符(?)代替了:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic{ //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } }
我们知道Ingeter
是Number
的一个子类,同时在特性章节中我们也验证过Generic
与Generic
实际上是相同的一种基本类型。那么问题来了,在使用Generic
作为形参的方法中,能否使用Generic
的实例传入呢?在逻辑上类似于Generic
和Generic
是否可以看成具有父子关系的泛型类型呢?
为了弄清楚这个问题,我们使用Generic
这个泛型类继续看下面的例子:
public void showKeyValue1(Genericobj){ Log.d("泛型测试","key value is " + obj.getKey()); }
GenericgInteger = new Generic (123); Generic gNumber = new Generic (456); showKeyValue(gNumber); // showKeyValue这个方法编译器会为我们报错:Generic // cannot be applied to Generic // showKeyValue(gInteger);
通过提示信息我们可以看到Generic
不能被看作为`Generic
的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic
类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic
和Generic
父类的引用类型。由此类型通配符应运而生。
我们可以将上面的方法改一下:
public void showKeyValue1(Generic> obj){ Log.d("泛型测试","key value is " + obj.getKey()); }
看到了很多文章中都会提起泛型数组,经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。
也就是说下面的这个例子是不可以的:
List[] ls = new ArrayList [10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List>[] ls = new ArrayList>[10];
这样也是可以的:
List[] ls = new ArrayList[10];
下面使用Sun的一篇文档的一个例子来说明这个问题:
List[] lsa = new List [10]; // Not really allowed. Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList (); li.add(new Integer(3)); oa[1] = li; // Unsound, but passes run time store check String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常, 但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明, 上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。 而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
List>[] lsa = new List>[10]; // OK, array of unbounded wildcard type. Object o = lsa; Object[] oa = (Object[]) o; Listli = new ArrayList (); li.add(new Integer(3)); oa[1] = li; // Correct. Integer i = (Integer) lsa[1].get(0); // OK
1.上界 extends T>不能往里存,只能往外取
不要太疑惑,其实很好理解,因为编译器只知道容器里的是Fruit或者Fruit的子类,但不知道它具体是什么类型,所以存的时候,无法判断是否要存入的数据的类型与容器种的类型一致,所以会拒绝set操作。
2.下界 super T>往外取只能赋值给Object变量,不影响往里存
因为编译器只知道它是Fruit或者它的父类,这样实际上是放松了类型限制,Fruit的父类一直到Object类型的对象都可以往里存,但是取的时候,就只能当成Object对象使用了。
3.无边界类型通配符(>) 等同于 上边界通配符 extends Object>,所以可以以Object类去获取数据,但意义不大。
4.下边界类型通配符( super 子类型>)下边界通配符 super 子类型> + 上边界通配符 extends Object>,所以可以以Object类去获取数据,但意义不大。
1. “?”不能添加元素
以“?”声明的集合,不能往此集合中添加元素,所以它只能作为生产者(亦即它只能被迭代),如下:
List> names = Lists.newArrayList("yiifaa");
// 通配符声明的集合,获取的元素都是Object类型
List
allNames.addAll(names);
// 只能以Object迭代元素
for(Object name: names) {
System.out.println(name);
}
2. “? extends T”也不能添加元素
以“? extends T”声明的集合,不能往此集合中添加元素,所以它也只能作为生产者,如下:
List extends String> names = Lists.newArrayList("yiifaa");
// 声明消费者
List
// 消费生产者的元素
allNames.addAll(allNames);
相对于以“?”声明的集合,“? extends T”能更轻松地迭代元素:
List extends String> names = Lists.newArrayList("yiifaa");
// 能更精确地确认元素类型
for(String name: names) {
System.out.println(name);
3. “? super T”能添加元素
在通配符的表达式中,只有“? super T”能添加元素,所以它能作为消费者(消费其他通配符集合)。
List super String> allNames = Lists.newArrayList("yiifaa");
List
// 可以直接添加泛型元素
allNames.addAll(names);
// 也可以添加通配符泛型元素
List extends String> names1 = Lists.newArrayList("yiifee");
allNames.addAll(names1);
8
针对采用“? super T”通配符的集合,对其遍历时需要多一次转型,如下:
// 只能获取到Object类型
for(Object name: allNames) {
// 这里需要一次转型
System.out.println(name);
}