Java基础:泛型及其擦除性、不可协变性

来源:互联网 发布:windows自带桌面图片 编辑:程序博客网 时间:2024/05/19 06:50

文章转载出自:http://blog.csdn.net/jiyiqinlovexx

1泛型语法:

泛型类: class ClassName<T>{}

泛型方法:public <T> void f(T x){}

基本指导原则:如果使用泛型方法可以取代将整个类泛型化,那么就应该使用泛型方法,因为它可以让事情更加清楚。

 

2为什么使用泛型?

在Java SE1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是:

(1)在编译时检查类型安全

(2)并且所有的强制转换都是自动和隐式的,提高代码的重用率

促成泛型出现最引人注目的一个原因就是为了创造容器类。

 

优先考虑泛型!!!

使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。

在设计新类型的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。

比如


客户端代码使用Stack的时候,如果不使用泛型,在取用Stack中的对象时还需要进行不安全的类型判断,类型转换等,而且代码还比较复杂。如果使用带泛型的Stack,客户端代码中就不需要进行类型转换了,直接使用而且不会复杂。

 

3泛型数组:

无法创建泛型数组,一般的解决方式是在任何想要使用泛型数组的地方使用ArrayList。

  1. public <T> voidtest()  
  2. {          
  3. //Cannotcreate a generic array of T  
  4. T[]tList = new T[10];  
  5. int[]iList = new int[10];  
  6.    
  7. //useArrayList<T>  
  8. ArrayList<T>sList = new ArrayList<T>();  
  9. }  

4类型擦除:

一、概念

类型擦除:将泛型类型中类型参数信息去除,只映射为一份字节码,在必要时进行类型检查和类型转换。

编译时通过两个步骤来对泛型类型的类型进行擦除:

1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。

2.移除所有的类型参数。

 

备注:擦除也是为什么泛型必须编译时进行类型检查的原因,因为运行时类型信息被擦除了。

 

二、擦除举例

(1)容器泛型类型擦除:

  1. ArrayList<Integer>l1 = new ArrayList<Integer>();  
  2. ArrayList<String> l2= new ArrayList<String>();  
  3. LinkedList<Integer>l3 = new LinkedList<Integer>();  
  4. List<String> l4 =new LinkedList<String>();  
  5.    
  6. System.out.println(l1.getClass().getName());  
  7. System.out.println(l2.getClass().getName());  
  8. System.out.println(l3.getClass().getName());  
  9. System.out.println(l4.getClass().getName());  
  10.    
  11. //output  
  12. java.util.ArrayList  
  13. java.util.ArrayList  
  14. java.util.LinkedList  
  15. java.util.LinkedList  
可以看到上面四个泛型类型的类型信息均被擦出掉了,比如ArrayList<Integer>和ArrayList<String>编译后生成的字节码是一份。

 (2)自定义泛型类型擦除:

再看一个自定义的泛型类:

  1. class TObject<T>  
  2. {  
  3. privateT obj;  
  4. publicvoid Set(T object)  
  5. {  
  6. this.obj= object;  
  7. System.out.println("T:"+object.getClass().getName());  
  8. }          
  9. }  
编译擦除后,生成的类是这样:

  1. class TObject  
  2. {  
  3. privateObject obj;  
  4. publicvoid Set(Object object)  
  5. {  
  6. this.obj= object;  
  7. }          
  8. }  
首先泛型参数T被向上替换为自身的顶级父类Object,然后将类型参数T去除。

 (3)自定义继承关系泛型类型擦除:

  1. class Manipulator<Textends SuperClass>  
  2. {  
  3.    
  4.   private T obj;  
  5.   public Manipulator(T x){  
  6.     obj = x;  
  7.   }  
  8.   public void doSomething(){  
  9.     obj.f();  
  10.    System.out.println(obj.getClass().getName());  
  11.   }  
  12. }  
首先将泛型参数T向上替换为上边界,然后去除泛型参数:

  1. class Manipulator  
  2. {   
  3.   private SuperClass obj;  
  4.   public Manipulator(SuperClass x){  
  5.     obj = x;  
  6.   }  
  7.   public void doSomething(){  
  8.     obj.f();  
  9.    System.out.println(obj.getClass().getName());  
  10.   }  
  11. }  

三、擦除原因:

泛型不是在java一开始就有的,擦除是java中泛型实现的一种折中手段

具体来说两个原因使得泛型代码需要类型擦除:

(1)引入泛型代码不能对现有代码类库产生影响,所以需要将泛型代码擦除为非泛型代码;

(2)当泛型代码被当做类库使用时,为了兼容性,不需要知道泛型代码是否使用泛型,所以需要擦除;

 

5不可协变:

(1)数组和泛型对比

数组是可协变的、泛型是不可协变的。

什么是可协变性?举个例子说明:

数组可协变(covariant)是指如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类。

泛型不可变的(invariant)是指List<Base>不会是List<Sub>的基类,两者压根没关系。

 

(2)泛型为什么不可协变

泛型“编译时进行类型检查(类型安全)”特性决定了其不可协变。

ArrayList<Object> objList = new ArrayList<Long>();

//can't compile pass

类型安全检查

Object[] objArray = new Long[10];

//compile OK

 

如果ArrayList<Long>类型对象可以赋值给ArrayList<Object>类型引用,那么就违反了泛型类型安全的原则。

因为如果编译器你这样做,会导致可以往容器中放置非Long型的对象。但是数组就无所谓,他不是类型安全的。

 

再看看下面代码,第一行编译错误是因为不可协变性,那么为什么第二行可以呢?

List<Type> listt = new ArrayList<SubType>();        

//can't compile pass

 

List<? extends Type> listt = new ArrayList<SubType>();

//OK

参考泛型通配符,这就是其作用

 

不可协变并不代表不能在泛型代码中将父类出现的地方使用子类代替,如下面代码是合法的:

ArrayList<Type> list = new ArrayList<Type>();

//Type is SuperClass

list.add(new SubType());

//SubType is SubClass

Type[] tt = new Type[3];

tt[0] = new SubType();

 

(3)数组可协变带来的问题:

数组的协变性可能会导致一些错误,比如下面的代码:

  1. public static voidmain(String[] args) {    
  2.    Object[] array = new String[10];    
  3.     array[0] = 10;    
  4. }       
它是可以编译通过的,因为数组是协变的,Object[]类型的引用可以指向一个String[]类型的对象。但是运行的时候是会报出如下异常的:

Exception in thread"main" java.lang.ArrayStoreException: java.lang.Integer

但是对于泛型就不会出现这种情况了:

  1. public static voidmain(String[] args) {    
  2.     List< Object> list = newArrayList< String>();    
  3.     list.add(10);    
  4. }   
这段代码连编译都不能通过。

 

6通配符:

通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。即通配符产生一部分原因来自突破不可协变的限制

可以认为通配符使得List<?>是List<AnyType>的基类,List<? extends  Type>是List<SubType>的基类。

  1. // collection1可以存放任何类型  
  2. Collection<?>collection1 = new ArrayList<String>();  
  3. collection1 = newArrayList<Integer>();  
  4. collection1 = newArrayList<Object>();  
  5.    
  6. //collection3表示它可以存放Number或Number的子类    
  7. Collection<?extends Number> collection3 = null;  
  8. collection3 = newArrayList<Number>();  
  9. collection3 = newArrayList<Double>();  
  10. collection3 = newArrayList<Long>();  
  11.    
  12. //collection4表示它可以存放Integer或Integer的父类    
  13. Collection<? superInteger> collection4 = null;  

  1. collection4 = newArrayList<Object>();  

0 0
原创粉丝点击