Java泛型详解(透彻)
定义
Java中的泛型在JavaSE5中引入。
所谓泛型,即参数化类型。就是说,类型是以参数的方式传入泛型类。
例如:
ArrayList<Integer> aaryList = new ArrayList<Integer>();
那么,类型参数就是Integer。
缘由
为什么要引入泛型呢,得看在没有泛型的情况下会存在什么样的问题。看下面这个非常常见的例子:
ArrayList a = new ArrayList(); a.add(1); a.add("2"); for(int i=0; i<a.size(); i++) { String strTmp = (String)a.get(i); }
点击运行,啊哦,ClassCastException!
看出来了吧,问题就是类型不安全,给你一个ArrayList,很难直接获取内部元素的类型,容易引发错误。
泛型设计中的坑
JavaSE5中怎么引入泛型呢?
原先没有泛型的时候使用方式是这样:
ArrayList a = new ArrayList();
如果老的方式直接不支持了,那么基于Java做得那么多类库咋办,岂不是没法直接迁移至新版本的jdk?
No, No, No~这样会损失很多用户~
算了算了,非泛型和泛型两种方式并存吧。
那么泛型怎么在以前的基础上加入呢?
想来想去,还是在编译器保证吧!最简单!
ok,存在什么问题呢?看个demo:
class GenericsA<T> { T t = new T(); // Error }
天真的我以为可以这么搞,然而我却被告知: Type parameter 'T' cannot be instantiated directly.
好吧,原来这个T只是个占位符而已,类型信息并不会在运行时保存!类型信息会被擦除!
类型擦除
泛型的类型信息会被擦除,因此不要奢想在泛型类内部利用类型信息做任何事情。
至于使用泛型类的地方,编译器来做类型检查,但是也不足够安全。
看个例子:
public static void main(String[] args) { ArrayList arrayList = new ArrayList<String>(); arrayList.add("A"); arrayList.add("C"); arrayList.add(3); String tmp = (String)arrayList.get(2); }
啊哦,又是ClassCastException。
别忘记,ArrayList内部没有类型信息,在把ArrayList<String>转为ArrayList非泛型列表之后,连编译器也无法正常类型检查了!
类型边界
在编译器类型检查之前,泛型的类型信息会被擦除至某个边界。
普通泛型class ClassA<T>{}中类型会被擦除至Object。
class A<T> { T item; public A(T item) { this.item = item; } void test() { //此处,item只能调用属于Obeject类的方法 } }
通过泛型类型上设置边界,我们可以实现类型的限定:
class Animal{ void talk(){} } class B<T extends Animal> { T item; public A(T item) { this.item = item; } void test() { //此处,item可以调用属于Animal类的方法 item.talk(); } }
甚至,可以设置多个边界:
interface Eatable {void eat();} interface Drinkable {void drink();} class B<T extends Dog & Cat> { T item; void test() { //此处,item可以调用属于Eatable和Drinkable的方法 //不过,前提是,item同时是Eatable和Drinkable //(这里只能通过多个接口实现的方式,或者一个基类和多个接口的方式,因为你无法使一个类继承自多个父类) } }
通配符
所谓通配符,作用就是匹配多种类型。
1. 上界通配符: < ? extends B >
意思是,可以匹配类型A及其所有子类,即类型的上界是B,无下界。
举例子:
class A
{
public void print()
{
System.out.println("A");
}
}
class B extends A
{
@Override
public void print()
{
System.out.println("B");
}
}
class C extends B
{
@Override
public void print()
{
System.out.println("C");
}
}
class MyArrayList extends ArrayList<? extends B>
{
}
那么MyArrayList可以被赋值时:
MyArrayList myArrayList = new ArrayList<A>(); //ERROR MyArrayList myArrayList = new ArrayList<B>(); //OK MyArrayList myArrayList = new ArrayList<C>(); //OK
再来看下元素读写情况。
编译器内心独白:
- >>>> 既然设了上界通配符,那么我MyArrayList中的元素实际类型可能为B和B的任何子类,那么为了确保类型安全,只有同时继承自B和所有B的子类的类型才能add。
- >>>> 嗯?好像没有任何一种类型可用满足以上条件。
- >>>> 好吧,上界通配符泛型中,myArrayList不接受add方法。
- >>>> 哦,对了,任何以泛型类型为参数的方法我都不接受!
好吧,经历了编译器的内心一番搏斗,所有以泛型类型为参数的方法都没法使用了:
myArrayList.add(new B()); //ERROR
myArrayList.add(new C()); //ERROR
myArrayList.set(0, new B()); //ERROR
但是,get()、remove()方法等方法还是可以用的。
2. 下界通配符 ? super B
意思是,可以匹配类型B及其所有基类,即类型的下界是B,上界是Object。
接着举例子:
class MyArrayList extends ArrayList<? super B>
{}
那么:
MyArrayList myArrayList = new ArrayList<A>(); //OK MyArrayList myArrayList = new ArrayList<B>(); //OK MyArrayList myArrayList = new ArrayList<C>(); //ERROR MyArrayList myArrayList = new ArrayList<Object>(); //OK
上至Object,下至B,所有的实例都可以赋值给泛型为<?super B>的myArrayList。
再次看下元素读写情况。
编译器:
- >>>> 既然设了下界通配符,那么我MyArrayList中的元素实际类型可能为B和B的任何父类,那么为了确保类型安全,只有同时是B以及B的所有父类的类型实例才能add。
- >>>> 想一下,哪些类型符合条件呢?恩,好像所有B和B子类的实例都满足条件。例如。new B()既是B又是Object,new C()一样可以向上转型至B和Obejct....
- >>>> 当然,所有B及其子类的实例都可以add进去。
- >>>> 那么,get方法?
- >>>> myArrayList内部可能的类型那么多(B及其所有父类),get的返回值类型咋办呢,算了,用通用的Object吧,真实类型让那些码农自己判断去吧!