`

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  
一句话:泛型没有擦除,所有信息都在,只要声明时给了泛型,不需实例化对象,仅从类中就可以拿到。

相关推荐

    Java 范型Java 范型.doc

    Java 范型Java 范型

    Java 范型攻略篇

    虽然Java范型在编译时提供了类型安全,但在运行时,所有范型信息都会被擦除,只保留原始类型的信息。这是因为Java虚拟机(JVM)并不支持泛型,为了与现有的JVM兼容,Java采用了类型擦除(Type Erasure)的技术。这...

    JAVA范型指南中文版

    - **类型擦除**:Java泛型在编译后会进行类型擦除,实际生成的字节码中不会包含类型参数。这意味着在运行时,泛型对象实际上是无类型的,但编译时的类型检查仍然有效。 3. **泛型通配符** - **无界通配符**:`?`...

    java范型[参考].pdf

    此外,Java泛型还有擦除的概念,即在编译后,泛型信息会被消除,转而使用原始类型。这是因为Java的泛型是类型安全的,但不是运行时类型检查的。尽管如此,泛型仍然在编译时提供了类型检查,使得我们能够在开发阶段就...

    java范型[参照].pdf

    Java泛型是Java 5版本引入的一个重要特性,极大地增强了代码的类型安全性和可读性。泛型允许我们在编写代码时指定容器(如List、Set、Map等集合类)能够存储的数据类型,从而避免了不必要的类型转换,并在编译时期就...

    java 泛型类的类型识别示例

    在泛型类中,虽然类型参数在编译时被擦除,但仍然可以通过一些方式获取类型信息。 泛型类的基本结构如下: ```java public class MyClass&lt;T&gt; { private T myVariable; public MyClass(T value) { this....

    java范型视频

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是...

    Java程序设计范型和枚举PPT教案学习.pptx

    在Java中,范型也被称为参数化类型。例如,`HashMap, Value&gt;`就是一个范型类,其中`Key`和`Value`是类型参数,代表了存储的数据的键和值的类型。在创建`HashMap`实例时,我们可以指定具体的类型,如`HashMap, ...

    java范型学习

    Java 泛型是一种强大的语言特性,它在J2SE 5.0中引入,极大地提升了代码的类型安全性和效率。泛型允许我们在编写类、接口和方法时指定一种或多种类型参数,使得代码能够处理多种数据类型,同时避免了运行时的类型...

    用Java Socket实现一个简单的基于P2P范型的即时聊天系统。

    在本文中,我们将深入探讨如何使用Java的Socket编程来实现一个简单的基于P2P(Peer-to-Peer)范型的即时聊天系统。P2P网络架构允许每个节点既是客户端也是服务器,这种模式使得数据传输更加分散,提高了系统的可扩展...

    范型参考 (1).java

    范型参考 (1).java

    范型参考 (2).java

    范型参考 (2).java

    一个很好的范型立例题

    - Java范型在编译后会进行类型擦除,实际运行时并不保留类型参数信息,因此在运行时无法通过反射获取泛型类型。 - 擦除后,泛型类的实例将退化为无参数类型,但编译器会检查类型安全。 4. 泛型与多态 - 泛型与...

    C++多范型设计

    《C++多范型设计》是一本深入探讨C++编程语言中模板技术的专著,由知名软件工程师James O. Coplien撰写,并由鄢爱兰、周辉等翻译成中文版,ISBN号为9787508318240。这本书的核心主题是C++中的泛型编程,它是C++编程...

    Java 泛型(Generics)使用说明

    本例子说明演示了Java范型使用的动机、范型类的使用、范型方法的使用,以及范型的缺陷:类型擦除(type erasure).因为,现在Java的反射机制不能确定集合中的对象类型! 在实际应用中,如果能够灵活应用范型和反射...

    Java 实现泛型List

    Java 实现泛型List的源码,基本实现了List接口的全部所有方法。欢迎大家发表自己的观点和建议。

    论文研究-消息传递范型与C/S范型双范型的主数据管理机制 .pdf

    消息队列机制可以有效地管理异步消息传递,确保消息按照发送顺序被处理。锁机制则用于在处理关键数据时防止数据冲突,确保数据的完整性和一致性。 在MDM系统中,当某个分系统的数据发生变化时,系统会生成一个包含...

    范型程序设计与 STL.pdf

    《范型程序设计与 STL》是一本深入探讨C++编程中的关键概念和技术的书籍,主要聚焦于范型(Generic Programming)和标准模板库(Standard Template Library,简称STL)。范型编程是一种强大的软件开发方法,它允许...

    jdk1.5中的范型

    ### JDK 1.5 中的范型 #### 引言 自 JDK 1.5 开始,Java 语言正式引入了一项重要的特性——泛型(Generics),这使得 Java 成为了一个更为强大且灵活的语言。泛型允许开发人员编写出类型安全的通用类或方法,从而...

Global site tag (gtag.js) - Google Analytics