一、泛型擦除
在泛型代码内部,无法获得任何有关泛型参数类型的消息。
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
*///~
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; //在使用时再将其进行转型
}
}