`

Java范型真的被擦除了吗?

 
阅读更多

学习范型的第一课就被警告说,范型信息再编译之后是拿不到的,因为已经被擦除掉了。如果不深入研究,这个观点很容易给人以这样的错觉:只要代码里面用了范型的地方,编译之后,是没法拿到这部分信息的!

 

关于这点的错误之处,可以参考撒伽的这篇文章。 按他的解释是说:

位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。

这里,最根本的原因,还是因为jdk1.5之后,将范型类、方法、属性的信息,都固化到了编译之后的class文件中(具体的例子可以参考上面提高文章中反编译class文件之后拿到的信息),java本身也就有能力通过反射的API来获取这部分信息了。关于这个使用方法的实例,《Generic Data Access Objects》提供了演示。

 

不过,光看上面的文章,在自己操作处理范型信息时,java提供的API还是有点问题的。

这里,我们必须先搞清楚下面提到的几个概念:

 

  • type parameters:范型类或者范型接口声明里面的“范型参数”,比如:List<E>这个范型接口中,有一个type parameter,就是E。
  • parameterized types: “泛化类型”是指范型类或这范型接口本身,比如:List<String>就是一个具体的parameterized type。
  • actual type parameter:范型使用中,实际使用的真实类型。比如:List<String>中,String就是上面提到的E的actual type parameter。
  • raw type:踢掉“范型参数”之后,剩下的“裸类型”。比如:List<E>中,List就是List<E>的raw type。

了解了上面的几个基本概念之后,我们分别来看类(或者接口)、方法、属性对应的范型信息获取的API。

一、类(或者接口):参考下面是Class中getTypeParameters方法的实现。

 

    public TypeVariable<Class<T>>[] getTypeParameters() {
	if (getGenericSignature() != null) 
	    return (TypeVariable<Class<T>>[])getGenericInfo().getTypeParameters();
	else
	    return (TypeVariable<Class<T>>[])new TypeVariable[0];
    }

根据上面提到的概念,Class中这个方法本身的语义不难理解。

这里,返回了一个TypeVariable的数组。关于,TypeVariable接口,只有3个方法:String getName()、D getGenericDeclaration()、Type[] getBounds()。关于这三个方法的使用,具体来看一下下面的例子:

public class GenericT<T,E extends GenericT.B & Serializable> {

    private T innerT;

    private Map<String,T> nameMap;

    //just for demo
    public static class A{
    }
    //just for demo
    public static class B extends A{
    }

}


public class Main {

    public static void main(String[] args) {
        showGenericTypeInfo();
    }

    public static void showGenericTypeInfo(){
        TypeVariable<Class<GenericT>>[] typeVariable = GenericT.class.getTypeParameters();
        for (TypeVariable<Class<GenericT>> classTypeVariable : typeVariable) {
            System.out.println("TypeVariable.getName() : " + classTypeVariable.getName());
            System.out.println("TypeVariable.getClass() : " + classTypeVariable.getClass());
            StringBuilder sb = new StringBuilder( );
            for(Type type : classTypeVariable.getBounds()){
                sb.append( ((Class)type).getCanonicalName() ).append( "," );
            }
            System.out.println("TypeVariable.getBounds() : " + sb.substring(0,sb.length()-1));
            System.out.println("TypeVariable.getGenericDeclaration() : " + classTypeVariable.getGenericDeclaration());
        }
    }

} 

这里,为了演示TypeVariable.getBounds()方法,故意把GenericT的第二个type parameter继承了两个type(一个是他的内部类,一个是Serializable接口)。

运行上面的Main.main(),可以得到如下输出:

TypeVariable.getName() : T
TypeVariable.getClass() : class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
TypeVariable.getBounds() : java.lang.Object
TypeVariable.getGenericDeclaration() : class com.sky.www.generic.GenericT
TypeVariable.getName() : E
TypeVariable.getClass() : class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
TypeVariable.getBounds() : com.sky.www.generic.GenericT.B,java.io.Serializable
TypeVariable.getGenericDeclaration() : class com.sky.www.generic.GenericT

二、方法:(参考Method中提供的3个public的跟这个相关的方法)

 

    public Type[] getGenericParameterTypes() {
	if (getGenericSignature() != null)
	    return getGenericInfo().getParameterTypes();
	else
	    return getParameterTypes();
    }

    public Type getGenericReturnType() {
      if (getGenericSignature() != null) {
	return getGenericInfo().getReturnType();
      } else { return getReturnType();}
    }

      public Type[] getGenericExceptionTypes() {
	  Type[] result;
	  if (getGenericSignature() != null &&
	      ((result = getGenericInfo().getExceptionTypes()).length > 0))
	      return result;
	  else
	      return getExceptionTypes();
      }

这3个方法,分别对应获取方法的参数,返回值,异常的范型信息。注意,3个方法的返回值都是一个最上层的Type接口,实际使用时,就可能需要使用者自己去强转一下具体的子接口了。毕竟,Type只是个最高层抽象的类型接口,里面没有任何可以使用的方法,比较常用的子接口至少包括如下几个:

  • TypeVariable (java.lang.reflect):上面类的范型信息中提到过。
  • ParameterizedType (java.lang.reflect) :参考上面的概念,比如List<String>
  • WildcardType (java.lang.reflect):使用范型通配符?类型,比如List<? extends String>
  • GenericArrayType (java.lang.reflect) : 字面解释就是“范型数组类型”,主要代表的抽象实体是范型相关的数组。

上面四个最常用的子Type,前三个比较好理解,第四个GenericArrayType说起来有点拗口。(当然Type除了这四个子接口外,在常用的一个实现类就是Class本身了,这个不要忘记,下面的演示中也有示例)来看个下面的具体的例子,这个例子主要是以getGenericReturnType为基础展示的,其他两个完全类似:

 

public class GenericArrayTypeTest<T> {
    // component type is a type variable
    public T[] typeParameterDemo(){
        return null;
    }
    // component type is a parameteriazed type
    public List<String>[] listArrayDemo(){
        return null;
    }
    // component type is a int.class
    public int[] listInt(){
        return null;
    }
}

public class Main {

    public static void main(String[] args) {
        showGenericArrayType();
    }

    public static void showGenericArrayType(){
        for(Method method :GenericArrayTypeTest.class.getDeclaredMethods()){
            System.out.println("############################################");
            System.out.println("Method name : "+method.getName());
            Type type = method.getGenericReturnType();
            if(type instanceof GenericArrayType){
                Type componentType = ((GenericArrayType)type).getGenericComponentType();
                System.out.println("GenericArrayType's componentType is : "+componentType);
                if(componentType instanceof TypeVariable){
                    System.out.println(componentType+" is a TypeVariable!");
                    TypeVariable typeVariable = (TypeVariable)componentType;
                    System.out.println("TypeVariable.getGenericDeclaration() is : "+typeVariable.getGenericDeclaration());
                }else if(componentType instanceof ParameterizedType){
                    System.out.println(componentType+" is a ParameterizedType!");
                    ParameterizedType parameterizedType = (ParameterizedType)componentType;
                    System.out.println("It's raw type is : "+parameterizedType.getRawType());
                    System.out.println("It's owner type is : "+parameterizedType.getOwnerType());
                    for(Type type1 : parameterizedType.getActualTypeArguments()){
                        System.out.println("Actual Type is : "+type1);
                    }
                }
            }else if(type instanceof Class){
                System.out.println("GenericeReturnType is a class : "+type);
                Class cl = (Class)type;
                System.out.println("It's name is : " + cl.getCanonicalName());
                if(cl.isArray()) {
                    System.out.println("It is a array!");
                    Class<?> componentType = cl.getComponentType();
                    System.out.println("The component type of this array is : "+componentType);
                }
            }
            System.out.println("############################################");
        }
    }
}

上面一段程序的输出如下:

############################################
Method name : typeParameterDemo
GenericArrayType's componentType is : T
T is a TypeVariable!
TypeVariable.getGenericDeclaration() is : class com.sky.www.generic.GenericArrayTypeTest
############################################
############################################
Method name : listArrayDemo
GenericArrayType's componentType is : java.util.List<java.lang.String>
java.util.List<java.lang.String> is a ParameterizedType!
It's raw type is : interface java.util.List
It's owner type is : null
Actual Type is : class java.lang.String
############################################
############################################
Method name : listInt
GenericeReturnType is a class : class [I
It's name is : int[]
It is a array!
The component type of this array is : int
############################################

三、属性(Field里面只提供了一个public出来的方法,用于获得域属性的范型信息)

    public Type getGenericType() {
	if (getGenericSignature() != null)
	    return getGenericInfo().getGenericType();
	else
	    return getType();
    }
 关于这个API的使用,这里就不举例了,因为获得还是最上层的Type信息,所以具体使用时,还是跟上面Method的演示的差不错,通常涉及到转换成具体的子Type接口才能获得更为具体的信息。具体操作层面上,就是根据情况转换成上面的四个子接口之一或者Class来操作了。 

 

 

 

 

分享到:
评论
1 楼 TonyLian 2014-09-28  
一句话:泛型没有擦除,所有信息都在,只要声明时给了泛型,不需实例化对象,仅从类中就可以拿到。

相关推荐

Global site tag (gtag.js) - Google Analytics