[运行时获取模板类类型] Java 反射机制 + 类型擦除机制
2014-11-03 17:59
543 查看
给定一个带模板参数的类
class A<T>
{
}
如何在运行时获取 T的类型?
在C#中,这个很简单,CLR的反射机制是解释器支持的,大概代码为:
其根本原因在于: java语言是支持反射的,但是JVM不支持,因为JVM具有“Type erasure”的特性,也就是“类型擦除”。
但是Java无绝人之路,我们依旧可以通过其他方法来达到我们的需求。
方法1. 通过构造函数传入实际类别. 【有人说这个方法不帅气,但是不可否认,这种方法没有用到反射,但是最为高效,反射本身就不是高效的。】
方法2. 通过继承这个类的时候传入模板T对应的实际类:(定义成抽象类也可以)
继承类:
我相信很多人用过这样类似的代码,但是并不是每个人都真正了解了,为什么可以这样做。
光是看代码:
我们可以
却没有:
为什么呢?
stackoverflow上有个老外说:java 里如果 一个类继承了另外一个带模板参数的类,那么这个模板参数不会被“类型擦除”。而单一的一个类,其泛型参数会被擦除。
首先说明这种假设是错误的。我相信JCP一定会不会莫名其妙的有这种无厘头规定。如果这种说法成立,就等于:
能输出:D
但是实际情况是,没有输出D,输出的是 Object.
说明这跟是不是父类子类没关系。
那么究竟是什么原因,导致了,一个泛型类(含有模板参数的类),没有像C#中 GetGenericArguments()类似的getGenericClass()函数呢??
再来温习下java 中 “类型擦除” 的范畴。
我用自己的语言定义一下(未必精确,但求理同):
Java中所有除了【类声明区域】(区域划分如下)之外的代码中,所有的泛型参数都会在编译时被擦除。
如下的2个类:
在编译后的class文件代码为:(By Java De-compiler)
可以看到,【类声明区域】和java文件中的一模一样。而【类定义区域】中所有的泛型参数都被去掉了。
那么为啥这样呢?一个类,在编程中宿命的只有两大类:要么被继承,要么自己创建实例。直接用于创建实例时必在【类定义区域】,从而必定被擦除。只有被继承时,子类的实例信息中会存在一个夫类的泛型信息。
为何要有类型擦除?这涉及到Java语言的特性,JDK 从1.5(应该是)开始支持泛型,但是只能说是Java语法支持泛型了,JVM并不支持泛型,不少人笑称其为 “假泛型”。
虽然会被擦除,但是《Effective Java》 2th Edtion 还是建议大家在编程中,明确限定原型类,这样可以更好的约束代码,在编译期间提示。如果确实不在乎列表元素的类型是否一致,请使用 List<?>。
所以当我们使用
List<String>的时候,编译器看到的不是String,而是一个Object(java中所有类型都继承于Object)。
一旦【类定义区域】中的泛型参数被擦除了。那么使用这个模板类创建实例,运行时,JVM反射是无法获取此模板具体的类型的。
因此
像C#中 GetGenericArguments()类似的getGenericClass()函数,在java中毫无意义。
这里的毫无意义是指在上面所说的java和jvm的特性的基础上。(若jvm支持真泛型,那么这一切就有意义了)
为什么说他无意义,反证法:
假设这个函数存在,有意义,那么下面代码可以获取T.Class
这时候问题就来了,JVM执行的是.class文件,而不是.java文件,在JVM看来,这个class文件里没有说任何关于T的信息,现在你问我要T.class 我该拿什么给你?
这样一来,
这个函数永远都返回 java.lang.Object. 等于没返回。
所以这个函数没有必要存在了。
回头想想,之所以
getGenericSuperclass();有效,其本质在于
在子类的java文件中的【类声明区域】指定了T的真正类。
如:
public class B extends A<Integer>{
//...
}
这样一个小悬案就明朗了。
class A<T>
{
}
如何在运行时获取 T的类型?
在C#中,这个很简单,CLR的反射机制是解释器支持的,大概代码为:
namespace TestReflect { class Program<T> { public Type getTClass() { Type type= this.GetType(); Type[] tts = type.GetGenericArguments(); return tts[0]; } } }可是在Java中,答案是否定的,java中没有直接方法来获取T.Class. (至少JDK 1.7以前不可能)
其根本原因在于: java语言是支持反射的,但是JVM不支持,因为JVM具有“Type erasure”的特性,也就是“类型擦除”。
但是Java无绝人之路,我们依旧可以通过其他方法来达到我们的需求。
方法1. 通过构造函数传入实际类别. 【有人说这个方法不帅气,但是不可否认,这种方法没有用到反射,但是最为高效,反射本身就不是高效的。】
public class A<T> { private final Class<T> clazz; public A<T>(Class<T> clazz) { this.clazz = clazz; } // Use clazz in here }
方法2. 通过继承这个类的时候传入模板T对应的实际类:(定义成抽象类也可以)
class A<T>{ public Class getTClass(int index) { Type genType = getClass().getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { return Object.class; } <span style="white-space:pre"> </span> Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); <span style="white-space:pre"> </span> if (index >= params.length || index < 0) { <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>throw new RuntimeException("Index out of bounds"); <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span> if (!(params[index] instanceof Class)) { <span style="white-space:pre"> </span> return Object.class; <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span> return (Class) params[index]; } }
继承类:
public class B extends A<String> { public static void main(String[] args) { B bb =new B(); bb.getTClass();//即答案 } }
我相信很多人用过这样类似的代码,但是并不是每个人都真正了解了,为什么可以这样做。
光是看代码:
我们可以
getGenericSuperclass();
却没有:
getGenericclass();
为什么呢?
stackoverflow上有个老外说:java 里如果 一个类继承了另外一个带模板参数的类,那么这个模板参数不会被“类型擦除”。而单一的一个类,其泛型参数会被擦除。
首先说明这种假设是错误的。我相信JCP一定会不会莫名其妙的有这种无厘头规定。如果这种说法成立,就等于:
public class C<T> { } public class C1<T> extends C<T>{ public C1() { } public Class getTClass(int index) { Type genType = getClass().getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if (index >= params.length || index < 0) { throw new RuntimeException("Index outof bounds"); } if (!(params[index] instanceof Class)) { return Object.class; } return (Class) params[index]; } }直接调用C1来创建实体后:
public static void main(String[]args) { C1<D> a= new C1<D>(); System.out.println(a.getTClass(0)); }
能输出:D
但是实际情况是,没有输出D,输出的是 Object.
说明这跟是不是父类子类没关系。
那么究竟是什么原因,导致了,一个泛型类(含有模板参数的类),没有像C#中 GetGenericArguments()类似的getGenericClass()函数呢??
再来温习下java 中 “类型擦除” 的范畴。
我用自己的语言定义一下(未必精确,但求理同):
Java中所有除了【类声明区域】(区域划分如下)之外的代码中,所有的泛型参数都会在编译时被擦除。
public class/interface [类声明区域] { [类定义区域] }
如下的2个类:
public class E<T>{ public static void main(String []args) { List<String> a = new ArrayList<String>(); System.out.println("Done"); } } class SubE extends E<String>{ }
在编译后的class文件代码为:(By Java De-compiler)
public class E<T> { public static void main(String[] args) { List a = new ArrayList(); //区别在这一行 System.out.println("Done"); } }
class SubE extends E<String> { }
可以看到,【类声明区域】和java文件中的一模一样。而【类定义区域】中所有的泛型参数都被去掉了。
那么为啥这样呢?一个类,在编程中宿命的只有两大类:要么被继承,要么自己创建实例。直接用于创建实例时必在【类定义区域】,从而必定被擦除。只有被继承时,子类的实例信息中会存在一个夫类的泛型信息。
为何要有类型擦除?这涉及到Java语言的特性,JDK 从1.5(应该是)开始支持泛型,但是只能说是Java语法支持泛型了,JVM并不支持泛型,不少人笑称其为 “假泛型”。
虽然会被擦除,但是《Effective Java》 2th Edtion 还是建议大家在编程中,明确限定原型类,这样可以更好的约束代码,在编译期间提示。如果确实不在乎列表元素的类型是否一致,请使用 List<?>。
所以当我们使用
List<String>的时候,编译器看到的不是String,而是一个Object(java中所有类型都继承于Object)。
一旦【类定义区域】中的泛型参数被擦除了。那么使用这个模板类创建实例,运行时,JVM反射是无法获取此模板具体的类型的。
因此
像C#中 GetGenericArguments()类似的getGenericClass()函数,在java中毫无意义。
这里的毫无意义是指在上面所说的java和jvm的特性的基础上。(若jvm支持真泛型,那么这一切就有意义了)
为什么说他无意义,反证法:
假设这个函数存在,有意义,那么下面代码可以获取T.Class
class A<T>{ public Class getTclass() { return this.getGenericClasses()[0];//这里只有一个模板参数 } public static void main(String []args) { A<Integer> a= new A<Integer>(); System.out.print(a.getTclass()); } }这样的一段代码会被编译成:
class A<T>{ public Class getTclass() { return this.getGenericClasses()[0];//这里只有一个模板参数 } public static void main(String []args) { A a= new A(); System.out.print(a.getTclass()); } }
这时候问题就来了,JVM执行的是.class文件,而不是.java文件,在JVM看来,这个class文件里没有说任何关于T的信息,现在你问我要T.class 我该拿什么给你?
这样一来,
getGenericClasses()
这个函数永远都返回 java.lang.Object. 等于没返回。
所以这个函数没有必要存在了。
回头想想,之所以
getGenericSuperclass();有效,其本质在于
在子类的java文件中的【类声明区域】指定了T的真正类。
如:
public class B extends A<Integer>{
//...
}
这样一个小悬案就明朗了。
相关文章推荐
- java 反射机制 (获取父类泛型的类型)getGenericSuperclass
- 通过java类的反射机制获取类的属性类型
- 通过java类的反射机制获取类的属性类型
- java 反射机制(通过getGenericSuperclass()方法获取到父类泛型的类型)
- java自定义注解和运行时靠反射机制获取注解
- java 反射机制 (获取父类泛型的类型)getGenericSuperclass
- java学习笔记---类型信息(type information)、反射机制与动态代理
- c#反射机制学习和利用反射获取类型信息
- JAVA反射获取类的属性及类型
- java中利用反射机制绕开编译器对泛型的类型限制
- 黑马程序员--Java基础加强--14.利用反射操作泛型III【解析关于泛型类型的细节信息的获取方法】【Method与泛型相关的方法】【个人总结】
- Java自定义注解和运行时靠反射获取注解
- 利用反射机制动态获取对象属性名称及数据类型
- Java通过反射机制获取Class对象
- 使用Java反射(Reflection)机制获取对象
- c#反射机制学习和利用反射获取类型信息
- java基础-反射 --通过反射 获取泛型实际类型参数
- Java利用反射来获取一个方法的 范型化参数 Vector<Integer>的类型
- Java利用反射来获取一个方法的 范型化参数 Vector<Integer>的类型
- java 运行时类型识别(RTTI) - 2 - 反射