Thinking in java学习笔记—泛型(擦除)

一、泛型擦除

在泛型代码内部,无法获得任何有关泛型参数类型的消息。

import java.util.Arrays;

class AA<T>{}
class CC<Q>{}
class B{}

public class Generic {
	public static void main(String[] args){
		AA<B> a  = new AA<B>();
		CC<B> c  = new CC<B>();
 		System.out.println(Arrays.asList(a.getClass().getTypeParameters()));
 		System.out.println(Arrays.asList(c.getClass().getTypeParameters()));
	}
}/*Output:
[T]
[Q]
*///:~
上面这段代码,通过反射我们查看在泛型类对象的参数类型,最后输出的结果只是用作参数占位符的标识符,这不是有用的信息。

Java泛型是使用擦除来实现的,这意味着当我们使用泛型时,任何具体的类型信息都将被擦除,唯一知道的就是我们在使用一个对象。

List<String>和List<Integer>在运行时实际上都是相同的类型。


二、应对擦除

在基于擦除性的实现中,泛型类被当做第二类型处理,即不能再某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现。

任何在运行时需要知道确切类型信息的操作都将无法工作:

public class Erased<T> {
	private final int SIZE =  100;
	public static void f(Object arg){
		if(arg instanceof T){}   //Error
		T var = new T();         //Error
		T[] array = new T[SIZE]; //Error
		T[] array = (T)new Object[SIZE];  //Unchecked warning	
	}
}

可以通过引入类型标签来对擦除进行补偿。这意味着需要显式地传递类型的Class对象。

import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayMaker<T>{
	private Class<T> kind;                   //在泛型类内部,我们用一个class域来保存类型class对象。
	public ArrayMaker(Class<T> kind){
		this.kind = kind;
	}
	@SuppressWarnings("unchecked")
	public T[] creat(int size){
		return (T[])Array.newInstance(kind,size);  //反射机制  ,当我们想要创建一个泛型数组时,
		                                           //可以通过reflect里面的Array.newInstance(Class<?> component,int length)
		                                           //来达成目的
	}
	public static void main(String[] args){
		ArrayMaker<String> make = new ArrayMaker<String>(String.class);
		String[] str = (String[])make.creat(10); 
		
		System.out.println(Arrays.toString(str));
	}
}
接下来我们来看一下面对擦除,我们有哪些解决方法


1、创建类型示例

(1)传递一个工厂对象,并使用它来创建新的实例

最便利的工厂方法就是就是Class对象

class TestClass{
	public TestClass(){
		System.out.println("I am TestClass constructor");
	}
}

public class ClassAsFactory<T>{
	private T t;
	public ClassAsFactory(Class<T> cla){
		try{
			t = cla.newInstance();
		}catch(InstantiationException e){
			e.printStackTrace();
		}catch(IllegalAccessException e){    
			e.printStackTrace();
		}
	}
	public static void main(String[] args){
		ClassAsFactory newclass = new ClassAsFactory(TestClass.class);
	}

}/*Output
I am TestClass constructor
*///~


(2)但是碰到没有默认构造器的类型,这种方式就会失效了,我们可以使用显示的工厂方法

import java.util.Random;

interface Factory<T>{                                                  //这是工厂方法的工厂接口
	T creat();
}
class IntegerFactory implements Factory<Integer>{                     //实现了Factory接口的类
	public Integer creat(){
		return new Integer(new Random(47).nextInt());
	}
}

class InnerFactory{
	public static class innerFactory implements Factory<InnerFactory>{  //内部类实现Factory
		public InnerFactory creat(){
			return new InnerFactory();
		}
	}
}

public class FactoryCreatTest<T> {
	private T t;
	public <F extends Factory<T>> FactoryCreatTest(F f){  //将参数类型限制为Factory<T>
		t = f.creat();                                    //通过工厂方法,我们实现了泛型实例的创建
	}
	public static void main(String args){
		new FactoryCreatTest(new IntegerFactory());
		new FactoryCreatTest(new InnerFactory.innerFactory());
		
	}
}
2、泛型数组

前面说过,可以利用反射机制里面的Array.newInstance(CLass<?> component,int lenght)来在泛型类内部创建泛型数组,但是这种方法要求传递类型标签,

即类型的Class对象。

数组将跟踪它们的实际类型,而这个类型是在数组被创建时确定的。

因此下面通过创建Object数组然后将其转型为某个类型会产生ClassCaseException

class AFGone<T>{
}

public class ArrayOfGeneric {
	static int SIZE =  100;
	static AFGone<Integer>[] afg;
	public static void main(String[] args){
		@SuppressWarnings("unchecked")
		//Compiles:produces ClassCastException:
	    //!afg = (AFGone<Integer>[])new Object[SIZE];
	}
}




public class GenericArray<T> {
  private T[] array;
  @SuppressWarnings("unchecked")                     //如果将SupressWarnings去掉,在这里就会产生警告。
  public GenericArray(int sz) {
    array = (T[])new Object[sz];                    
  }
  public void put(int index, T item) {
    array[index] = item;
  }
  public T get(int index) { return array[index]; }
  // Method that exposes the underlying representation:
  public T[] rep() { return array; }<span style="white-space:pre">	</span>
  public static void main(String[] args) {
    GenericArray<Integer> gai =
      new GenericArray<Integer>(10);
    // This causes a ClassCastException:            //因为运行时的实际类型是Object[]
    //! Integer[] ia = gai.rep();
    // This is OK:
    Object[] oa = gai.rep();
  }
} ///:~

下面是几种解决方法:

(1)使用ArrayList
(2)通过创建有一个被擦除类型的数组,然后对其进行转型

class AFGone<T>{
}

public class ArrayOfGeneric {
	static int SIZE =  100;
	static AFGone<Integer>[] afg;
	public static void main(String[] args){
		afg = (AFGone<Integer>[])new AFGone[SIZE];           //创建泛型数组的方式是创建一个被擦除类型的新数组,然后对其进行转型。
		System.out.println(afg.getClass().getSimpleName());
	}

}
上面这段代码,类型AFGone<Integer>在运行时将被擦除到AFGone。


(3)最后,我们可以在内部将数组贮存为Object[],当我们要返回具体类型时,再将他进行转型。

public class ArrayOfGeneric<T> {
	static int SIZE =  100;
	static Object[] afg;
	@SuppressWarnings("unchecked")
	public T[] get(){
		return (T[])afg;                  //在使用时再将其进行转型
	}
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值