首页 > 代码库 > Java使用泛型的困顿
Java使用泛型的困顿
一、前言
平时比较少接触泛型,之前也略微看过,自己也动手写过一些easy的demo,感觉也没啥难度,所以也就没怎么去深究,近来偶然的兴头去翻了翻相关的知识,突然发现,我勒个去,为嘛上面说的某些点我半天反应不过来?!毕业快三年了,这个盲点必须扫掉~~~本文不针对泛型做过多的举例,只是对于其中比较难以理解的部分做解读,不了解泛型的童鞋,自行百度~
二、正文
先说说什么是泛型,泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。(这边需要说明一点:泛型方法和泛型类没有必然联系,普通类里面也可以有泛型方法)~
从我的角度,曾经有困惑过我的问题有两个,其一:List<Father>和List<Son>之间的关系(Son是Father的子类);其二:为什么“List<? extends SomeClass> a”引用的对象只能使用get方法,不能add方法(编译报错,不过add(null)可以,后面会讲到),为什么“List<? superSomeClass> a”引用的对象只能使用add方法,不能使用get方法?
先说说第一个问题,算了,感觉这样说不下去了,还是要介绍下“泛型”出现的背景,听说在JDK1.5之前,Java泛型程序设计是用继承来实现的,因为Object类是所用类的基类,所以只需要维持一个Object类型的引用即可,就比如ArrayList只维护一个Object引用的数组:
public class ArrayList//JDK1.5之前的 { public Object get(int i){......} public void add(Object o){......} ...... private Object[] elementData; }
这样会有两个问题:
1、没有错误检查,可以向数组列表中添加类的对象
2、在取元素的时候,需要进行强制类型转换
这样,很容易发生错误,比如:
/**jdk1.5之前的写法,容易出问题*/ ArrayList arrayList1=new ArrayList(); arrayList1.add(1); arrayList1.add(1L); arrayList1.add("asa"); int i=(Integer) arrayList1.get(1);//因为不知道取出来的值的类型,类型转换的时候容易出错
所以,在JDK1.5之后,加入了泛型来解决类似的问题,例如在ArrayList中使用泛型:
/** jdk1.5之后加入泛型*/ ArrayList<String> arrayList2=new ArrayList<String>(); //限定数组列表中的类型 // arrayList2.add(1); //因为限定了类型,所以不能添加整形 // arrayList2.add(1L);//因为限定了类型,所以不能添加整长形 arrayList2.add("asa");//只能添加字符串 String str=arrayList2.get(0);//因为知道取出来的值的类型,所以不需要进行强制类型转换
还要明白的是,泛型特性是向前兼容的。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类(比如 HashMap 和 ArrayList)的现有代码可以继续不加修改地在 JDK 1.5 中工作。
针对问题一解答:
Father是Son的父类,那么List<Father>会是List<Son>的父类吗?答案:不是!对于初学者来说,这个结论感觉就是辣眼睛~要理解这个问题,就先需要理解泛型出现的背景,很大程度上是为了避免强制类型转换导致的不兼容问题。(我这边是使用List这个类来做说明,其实完全可以建一个泛型类Tes<T>,然后以Test<Father>和Test<Son>来举例,初学者不必纠结于此,只是List是大家熟知,所以以此举例)~我们知道List里面有两个方法一个是get,一个是add,这两个方法的声明如下形式:
public abstract interface List<E> extends Collection<E> { .................... public abstract E get(int paramInt); public abstract boolean add(E paramE); .................... }
我们可以举个实际的例子来说明下,假设List<Number>是List<Integer>和List<Float>的父类:
import java.util.ArrayList; import java.util.List; public class TestMain { public static void main(String[]args) throws Exception{ List<Integer> intList = new ArrayList<Integer>(); intList.add(10); List<Number> numberList = intList;//此处编译报错//numberList.get(0); ------1 } }
那么在“1”处,得到的结果是Number还是Integer呢?由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换,显然,这与泛型的理念矛盾,因此,SomeClass<Father>不能视为SomeClass<Son>的父类(特别说明:Fahter<T>是Son<T>的父类,这个和现在的结论不矛盾)~
针对问题二的解答:
看下下面这段代码(摘录别人的,详见“参考链接”部分),注释里面说的已经非常清楚,最核心的一句话就是为了确保类型能够兼容!
先看看这行代码:List<? extends Pet> list= new ArrayList<Dog>(),很多人可能会问,我这边不是已经指定泛型的具体类型为Dog吗?但是从编译器的反馈来看,只知道左边方括号内的内容“? extends Pet”,所以编译器只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保在运行时类型兼容(这个我是这样理解的,在运行时,必定会根据右边的表达式知道泛型的实际类型为Dog,如果在代码里面使用add(new DogOther()),DogOther也是Pet的一个子类,这边add的动作相当于将DogOther类型的对象实例,赋值给Dog类型的引用,DogOther和Dog并不兼容,那么运行时必定报错,所以不允许),所以不允许使用add函数~那为什么又允许get呢?首先我们要明白,既然此处不能add,那么如果list有值必定是之前就有一个有确定值的List对象实例假设是list01,这个list01的泛型必定是确定的,然后list01赋值给list,在使用list.get的时候返回的值应该是一个确定类型的对象,所以可以使用get~
再来看看List<? super Dog> list2 = new ArrayList<Dog>(),同样,在编译期间,编译器并不知道泛型的具体类型是什么,只是知道是Dog类型或者其超类,如果在运行时,可以知道泛型的类型为Dog,在调用list2.add(new PettyDog())的时候,相当于将PettyDog的对象实例,赋值给Dog,根据里氏替换原则,这样是允许的,所以这边使用add是合法的~那为什么此处调用get报错呢?这是因为add方法可以往list2里面设置很多Dog或者Dog类型的子对象,那么在调用get的时候返回的到底是什么呢?因为有多种可能,又要进行类型判断,且进行强制类型转换,显然,这与泛型的理念又发生矛盾~
public class TestMain { public static void main(String[] args) { Item<Dog> item = new Item<Dog>(new Dog()); item.get(); Item<? extends Pet> item2 = new Item<Dog>(new Dog()); item2.get().out(); /**知道某种限制,但是不知道具体是什么**/ List<? extends Pet> list = new ArrayList<Dog>(); /** * The method add(capture#2-of ? extends Pet) in the type List<capture#2-of ? extends Pet> * is not applicable for the arguments (Dog) * list.add(new Dog()); * 从错误代码得知,编译期list的类型是List<? extends Pet>,所以通配符还是个未知类型; * 只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保类型兼容; * 即使list.add(new Object()); (Object类型与Pet的子类不兼容) 也是不可以的; * list.add(null);这个可以;所以对于使用通配符的泛型,只能“取”不能“加”; * 编译器只允许加null;因为只有 Pet的子类 = null;等式成立; */ //list.get(0); List<? super Dog> list2 = new ArrayList<Dog>(); /** * 这关系到泛型的边界(bounds),list通配符使用的是上边界,无法确定具体的类; * 这里list2用的是下边界,取放的是Dog某种超类,但是并不知道具体的超类是什么; * 我们只知道Dog的子类可以跟Dog超类类型兼容 * 所以这种形式的通配符只能“存”不能“取” */ list2.add(new Dog()); /** * list2.add(new Object()); */ list2.add(new PettyDog()); /**ClassCastException**/ //list2.add((Dog) new Run()); } } interface Pet { public void out(); } class Item<T extends Pet> { T item; public Item(T item) { this.item = item; } public T get() { return item; } public void put(T item) { } } class Run { } class Dog extends Run implements Pet { @Override public void out() { System.out.println("The Dog!"); } public void getMsg(Item<?> item) { item.get(); /** *The method put(capture#3-of ?) in the type Item<capture#3-of ?> * is not applicable for the arguments (Item<capture#4-of ?>) * capture:占位符被称为这个特殊通配符的捕获 * item.put(item); */ } } class PettyDog extends Dog { }
三、参考链接
http://www.cnblogs.com/ljxe/p/5521840.html
http://blog.csdn.net/lonelyroamer/article/details/7864531
https://my.oschina.net/u/782865/blog/198906
Java使用泛型的困顿