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吧,真实类型让那些码农自己判断去吧!

 

posted @ 2019-01-13 23:39  Xinxin_Brian  阅读(3075)  评论(0编辑  收藏  举报