Java泛型学习二:擦除

一.开篇

     上文http://zy19982004.iteye.com/blog/1976993中提到“NewCollections.map() return Map<Object, Object>, but not  Map<Integer, String>”,为什么呢?对擦除的理解将是对泛型理解的关键。

 

二.擦除的概念

     《Thinking in Java》里说道“在泛型代码内部,无法获得任何有关泛型参数类型的信息”。

     《Java核心技术》里说道“虚拟机没有泛型类型对象-所有对象都属于普通类”。

  1. Java泛型是使用擦除(擦除实际类型参数,替换为限定类型)来实现的。这意味着当你使用泛型时,泛型实际类型参数只有在静态类型检查期间才出现,在此之后,任何具体的类型信息都被擦除,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时是相同的类型,这两种类型都被擦除为它们的原生类型List。
  2. 一个例子,看看运行时,类型参数是什么样子。你能够发现的则是用作参数占位符的标识符,这些对我们没什么用。
    package com.jyz.study.jdk.generic;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 在泛型代码内部,无法获得任何有关泛型参数类型的信息
     * @author JoyoungZhang@gmail.com
     *
     */
    public class ClassTypeParameters {
    	
    	static class Frob<T>{}
    	static class FrobF<T extends Number>{}
    	static class FrobPM<P,M>{}
    	
    	private static List list1 = new ArrayList();
    	private static List<Integer> list2 = new ArrayList<Integer>();
    	private static Map<Integer, Integer> map1 = new HashMap<Integer, Integer>();
    	
    	
    	static Frob f1 = new Frob();
    	static FrobF<Integer> f2 = new FrobF<Integer>();
    	static FrobPM<Integer, Double> f3 = new FrobPM<Integer, Double>();
    	
    
    	//Calss.getTypeParameters()将返回一个TypeVariable对象数组
    	//表示有泛型声明所声明的形式类型参数
    	public static void main(String[] args) {
    		System.out.println(Arrays.toString(list1.getClass().getTypeParameters()));
    		System.out.println(Arrays.toString(list2.getClass().getTypeParameters()));
    		System.out.println(Arrays.toString(map1.getClass().getTypeParameters()));
    		
    		System.out.println(list1.getClass().getSimpleName());
    		System.out.println(list2.getClass().getSimpleName());
    		
    		System.out.println(Arrays.toString(f1.getClass().getTypeParameters()));
    		System.out.println(Arrays.toString(f2.getClass().getTypeParameters()));
    		System.out.println(Arrays.toString(f3.getClass().getTypeParameters()));
    	}
    	
    }
    
    
    输出结果
    [E]
    [E]
    [K, V]
    ArrayList
    ArrayList
    [T]
    [T]
    [P, M]
     

三.为什么要使用擦除

     核心动机:使得泛型化的客户端代码可以使用非泛型化的类库,非泛型化的客户端代码可以使用泛型化的类库。这个被称为“兼容迁移性”。这也从侧面反应了,前期的设计多么重要,倘若JDK1.0就将泛型纳入其中,必将是Java使用者的一大福音。

 

三.擦除原则

  1. 无限定的形式类型参数将被替换为Object。比喻List<T> T是无限定的,被替换为Object。
  2. 有限定的形式类型参数将被替换为第一个限定类型。比喻List<T extends Comparable & Serializable>,T被替换为Comparable,也称T被擦除到了Comparable。
  3. 需要注意的是,泛型擦除的对象是实际参数,也就是说是GenericClass<String, Integer>还是GenericClass<StringBuffer, Number>等具体类型参数被擦除到了ErasureClass,并不是GenericClass<K, V extends Number>被擦除到了ErasureClass。但有时候我们也说GenericClass<K, V extends Number>被擦除到了ErasureClass,甚至可以说GenericClass<?,?>被擦除到了ErasureClass,这并不妨碍我们理解,就好像说所有水果都好吃,自然苹果也是好吃的。
    package com.jyz.study.jdk.generic;
    
    /**
     * 擦除存在于泛型类,也存在于泛型方法
     * 两者擦除原则一样
     * 1.声明形式参数的部分“消失”
     * 2.无限定的形式类型参数将被替换为Object,有限定的形式类型参数将被替换为第一个实际类型参数
     * @author JoyoungZhang@gmail.com
     *
     */
    public class AfterErasure {
    	
    }
    
    class GenericClass<K, V extends Number>{
    	private K object1;
    	private V object2;
    	private K get1(){
    		return object1;
    	}
    	private V get2(){
    		return object2;
    	}
    	
    	private  <KK> KK singleMethod1(KK object){
    		return object;
    	}
    	private  <VV extends Number> VV singleMethod2(VV object){
    		return object;
    	}
    }
    
    class ErasureClass{
    	private Object object1;
    	private Number object2;
    	private Object get1(){
    		return object1;
    	}
    	private Number get2(){
    		return object2;
    	}
    	
    	private Object singleMethod1(Object object){
    		return object;
    	}
    	private Number singleMethod2(Number object){
    		return object;
    	}
    }
    
     

四.擦除的问题

  1. 既然擦除了类型参数的信息,那编译器是怎么确保方法或类中使用的类型的内部一致性呢?下面这个例子1和2产生的字节码是相同的,一方面验证了上面说的擦除原则;另外一方面说明了泛型工作的地方,称之为边界,在边界处,对传递进来的值就行额外的编译器检查,并插入对传递出去值的转型。 
    package com.jyz.study.jdk.generic;
    
    /**
     * 1 2字节码相同
     * 边界(泛型切入点):对传递进来的值就行额外的编译期检查,并插入对传递出去的值的转型
     * @author JoyoungZhang@gmail.com
     *
     */
    public class TestGenericCheckpoint {
        public static void main(String[] args) {
    	//1
    	GenericHolder<String> gh = new GenericHolder<String>();
    	gh.set("sa");//边界 编译器check
    	String sa1 = gh.get();//运行期间仍会checkcast
    	//2
    	SimpleHolder sh = new SimpleHolder();
    	sh.set("sa");//编译器不check任何东西
    	String sa2 = (String) sh.get();//运行期间checkcast
        }
    }
    
    class GenericHolder<T>{
        private T object;
        public void set(T object){
    	this.object = object;
        }
        public T get(){
    	return object;
        }
    }
    
    class SimpleHolder{
        private Object object;
        public void set(Object object){
    	this.object = object;
        }
        public Object get(){
    	return this.object;
        }
    }
    
     
  2. 擦除后显式的引用运行时类型的操作都将无法工作,包括转型,instanceof,new。有什么补救措施?可以采用类型标签。 isInstance代替 instanceof;newInstance代替new,注意newInstance需要class对象具有默认构造函数。
    //Determines if the specified <code>Object</code> is assignment-compatible
    //     * with the object represented by this <code>Class</code>.  
    //This method is
    //     * the dynamic equivalent of the Java language 
    //<code>instanceof</code>
    //     * operator.
    public native boolean isInstance(Object obj);
    
    //Creates a new instance of the class represented by this <tt>Class</tt>
    //     * object.
    public T newInstance() {...}
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值