java实现泛型检索

在JDK5中引入Java泛型之后, Java泛型Swift成为许多Java程序的组成部分。 但是,乍一看似乎很简单的Java泛型,程序员很快就会迷失此功能。

大多数Java程序员都知道Java编译器的类型擦除 。 一般而言,类型擦除意味着有关Java类的所有通用类型信息在其源代码的编译过程中都会丢失。 这是对Java向后兼容性的致敬:Java类的所有通用变体在正在运行的Java应用程序中共享一个表示。 如果ArrayList <String>的实例必须记住其泛型类型为String类型,则它必须将该信息存储在其功能描述中的某个位置,以指示例如List.get实际上返回String类型。 (通过功能描述,我指的是在类的所有实例之间共享的属性。这包括例如方法或字段定义。与功能描述相反,每个实例各自的实例状态存储在其对象表示中。 )因此ArrayList <String>实例的功能描述由其类ArrayList.class表示。 但是,由于ArrayList.class实例与其他实例(也可以是ArrayList <Integer>类型)共享,因此这将需要具有ArrayList.class的两个不同版本。 但是,这种类表示形式的修改对于较旧的JRE来说是无法理解的,因此会破坏Java应用程序的向后兼容性。 因此,以下比较将始终成功:

assert new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass();

由于这种比较是在运行时进行的,而该运行时已经擦除了类的通用类型,因此该比较将ArrayList.class == ArrayList.class转化为琐碎的事情。 更具体地说,正在运行的应用程序将确定ArrayList.class等于其自身,并返回true,而不管String.class!= Integer.class。 这是Java与其他编程语言(例如C ++)的主要区别,也是人们普遍抱怨Java的原因。 (从学术上讲,C ++实际上并不知道泛型类型。相反, C ++提供了与泛型相似的模板 。)

到目前为止,这对于许多开发人员而言并不是什么新鲜事物。 但是,与普遍看法相反,有时甚至在运行时也可能检索通用类型信息。 在解释之前,让我们看一个例子。 为此,我们定义以下两个类:

class MyGenericClass<T> { }
class MyStringSubClass extends MyGenericClass<String> { }

MyGenericClass具有通用类型T的单个参数。MyStringSubClass扩展了该通用类,并将T = String分配为其类型参数。 结果,Java编译器能够在其子类 MyStringSubClass的字节码中存储有关超类MyGenericClass的通用参数类型String的信息。 可以在不破坏向后兼容性的情况下实现此修改,因为此信息仅存储在已编译类的字节码的区域中,而旧JRE版本会忽略该信息。 同时,由于为MyStringSubClass的所有实例设置了T = String,因此MyStringSubClass的所有实例仍可以共享单个类表示形式。

但是,如何获取存储在字节码中的信息呢? Java API提供了Class.getGenericSuperclass方法,该方法可用于接收Type类型的实例。 如果直接超类实际上是泛型的,则返回的实例的类型另外为ParameterizedType,并且可以强制转换为该实例。 (类型不过是标记接口 。实际实例将是内部ParameterizedTypeImpl类的实例,但是您应始终将其强制转换为该接口。)由于强制转换为ParameterizedType接口,您现在可以调用方法ParameterizedType.getActualTypeArguments检索再次为Type类型的数组。 泛型超类的任何泛型类型参数将包含在此数组中,与类型定义中的索引相同。 任何表示非泛型类的Type实例都只是Java类类的实现。 (假定您不处理返回类型为GenericArrayType的数组。为简单起见,本文将跳过此方案。)

现在,我们可以利用这些知识来编写实用函数:

public static Class<?> findSuperClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Class<?> subClass = instance.getClass();while (subClass != subClass.getSuperclass()) {// instance.getClass() is no subclass of classOfInterest or instance is a direct instance of classOfInterestsubClass = subClass.getSuperclass();if (subClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) subClass.getGenericSuperclass();return (Class<?>) parameterizedType.getActualTypeArguments()[parameterIndex];
}

该函数将浏览实例的类层次结构,直到将classOfInterest识别为层次结构中的下一个直接子类。 在这种情况下,将使用Class.getGenericSuperclass方法检索此超类。 如上所述,此方法以包装表示形式(ParamererizedType)返回类的超类,该表示形式包含在子类中找到的泛型类型。 这使我们能够成功运行以下应用程序:

Class<?> genericType = findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);
assert genericType == String.class;

但是请注意

findSuperClassParamerterType(new MyGenericClass<String>(), MyGenericClass.class, 0)

在此实现中将引发异常。 如前所述:通用信息只能在子类的帮助下进行检索。 但是,MyGenericClass <String>不是MyGenericClass.class的子类,而是具有泛型参数的直接实例。 但是,如果没有显式的子类,就没有<something> .class表示形式来存储String参数。 因此,这次,在编译过程中无法删除通用类型。 因此,如果打算对一个类执行此类查询,则最好将MyGenericClass定义为抽象。

但是,由于到目前为止存在许多陷阱,我们仍未解决问题。 为了说明原因,请考虑以下类层次结构:

class MyGenericClass<T> { }
class MyGenericSubClass<U> extends MyGenericClass<U>
class MyStringSubSubClass extends MyGenericSubClass<String> { }

如果我们现在打电话

findSuperClassParameterType(new MyStringSubClass(), MyGenericClass.class, 0);

将会引发异常。 但是为什么会这样呢? 到目前为止,我们假定MyGenericClass的类型参数T存储在直接子类中。 在我们的第一个示例中,这是MyStringSubClass,它映射了通用参数T = String。 相反,现在MyStringSubSubClass存储引用U = String,而MyGenericSubClass只知道U =T。但是,U不是实际类,而是Java类型TypeVariable的类型变量。 如果要解析此层次结构,则必须解析所有这些依赖项。 这可以通过调整示例代码来实现:

public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Map<Type, Type> typeMap = new HashMap<Type, Type>();Class<?> instanceClass = instance.getClass();while (classOfInterest != instanceClass.getSuperclass()) {extractTypeArguments(typeMap, instanceClass);instanceClass = instanceClass.getSuperclass();if (instanceClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass();Type actualType = parameterizedType.getActualTypeArguments()[parameterIndex];if (typeMap.containsKey(actualType)) {actualType = typeMap.get(actualType);}if (actualType instanceof Class) {return (Class<?>) actualType;} else {throw new IllegalArgumentException();}private static void extractTypeArguments(Map<Type, Type> typeMap, Class<?> clazz) {Type genericSuperclass = clazz.getGenericSuperclass();if (!(genericSuperclass instanceof ParameterizedType)) {return;}ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;Type[] typeParameter = ((Class<?>) parameterizedType.getRawType()).getTypeParameters();Type[] actualTypeArgument = parameterizedType.getActualTypeArguments();for (int i = 0; i < typeParameter.length; i++) {if(typeMap.containsKey(actualTypeArgument[i])) {actualTypeArgument[i] = typeMap.get(actualTypeArgument[i]);}typeMap.put(typeParameter[i], actualTypeArgument[i]);}
}

上面的代码将通过在映射中跟踪任何链接的泛型类型定义来解析它们。 请注意,由于MyClass <A,B>扩展了MyOtherClass <B,A>定义了完全合法的子类型,因此仅按特定索引检查所有类型定义是不够的。

但是,我们仍然没有完成。 再次,我们将首先看一个示例:

class MyGenericOuterClass<U> {public class MyGenericInnerClass<U> { }
}
class MyStringOuterSubClass extends MyGenericOuterClass<String> { }MyStringOuterSubClass.MyGenericInnerClass inner = new MyStringOuterSubClass().new MyGenericInnerClass();

这次通过调用内部类的反思

findSuperClassParameterType(inner, MyGenericInnerClass.class, 0);

将失败。 乍一看,这似乎是必然的结果。 我们正在同一类的实例上的MyGenericInnerClass中寻找通用参数类型。 如上所述,这通常是不可能的,因为无法将通用类型信息存储在MyGenericInnerClass.class中。 但是,在这里,我们检查了泛型类的子类型的(非静态)内部类的实例。 MyStringOuterSubClass知道U =字符串。 在考虑MyGenericInnterClass的参数类型时,我们必须考虑到这一点。

现在,这里的事情变得非常棘手。 为了在外部类中找到泛型声明,我们必须首先掌握该外部类。 这可以通过反射和Java编译器增加了一个这样的事实来实现的合成 (这意味着没有源代码表示)字段此$ 0到任何内部类。 可以通过调用Class.getDeclaredField(“ this $ 0”)来检索此字段。 通过获取包含当前内部类的外部类的实例,我们可以自动访问其Java类。 现在,我们可以按上述步骤进行操作,并在封闭的类中扫描通用定义,然后将其添加到地图中。 但是,MyGenericOuterClass中U的类型变量表示形式将不等于MyGenericInnerClass中U的类型表示形式。 就我们所知,MyGenericInnerClass可以是静态的,并定义自己的通用变量名称空间。 因此,任何表示Java API中通用变量的TypeVariable类型都配备了genericDeclaration属性。 如果在不同的类中定义了两个泛型变量,则即使它们在同一个名称空间中共享一个名称,而另一类是另一类的非静态内部类,则TypeVariable表示形式的定义也不相等。

因此,我们必须执行以下操作:

  1. 首先,尝试在内部类超类层次结构中找到泛型类型。 就像处理非嵌套类一样。
  2. 如果无法解析类型:对于(非静态)内部类及其所有外部类,请尽可能完整地解析类型变量。 这可以通过相同的extractTypeArguments算法来实现,对于每个嵌套类,基本上为1 .。 我们可以通过检查this $ 0字段是否为内部类定义来获取外部类。
  3. 检查外部类之一是否包含具有相同变量名的通用变量的定义。 在这种情况下,您找到了所需的通用变量的实际类型。

在代码中,如下所示:

public static Class<?> findSubClassParameterType(Object instance, Class<?> classOfInterest, int parameterIndex) {Map<Type, Type> typeMap = new HashMap<Type, Type>();Class<?> instanceClass = instance.getClass();while (classOfInterest != instanceClass.getSuperclass()) {extractTypeArguments(typeMap, instanceClass);instanceClass = instanceClass.getSuperclass();if (instanceClass == null) throw new IllegalArgumentException();}ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass();Type actualType = parameterizedType.getActualTypeArguments()[parameterIndex];if (typeMap.containsKey(actualType)) {actualType = typeMap.get(actualType);}if (actualType instanceof Class) {return (Class<?>) actualType;} else if (actualType instanceof TypeVariable) {return browseNestedTypes(instance, (TypeVariable<?>) actualType);} else {throw new IllegalArgumentException();}
}private static Class<?> browseNestedTypes(Object instance, TypeVariable<?> actualType) {Class<?> instanceClass = instance.getClass();List<Class<?>> nestedOuterTypes = new LinkedList<Class<?>>();for (Class<?> enclosingClass = instanceClass.getEnclosingClass();enclosingClass != null;enclosingClass = enclosingClass.getEnclosingClass()) {try {Field this$0 = instanceClass.getDeclaredField("this$0");Object outerInstance = this$0.get(instance);Class<?> outerClass = outerInstance.getClass();nestedOuterTypes.add(outerClass);Map<Type, Type> outerTypeMap = new HashMap<Type, Type>();extractTypeArguments(outerTypeMap, outerClass);for (Map.Entry<Type, Type> entry : outerTypeMap.entrySet()) {if (!(entry.getKey() instanceof TypeVariable)) {continue;}TypeVariable<?> foundType = (TypeVariable<?>) entry.getKey();if (foundType.getName().equals(actualType.getName())&& isInnerClass(foundType.getGenericDeclaration(), actualType.getGenericDeclaration())) {if (entry.getValue() instanceof Class) {return (Class<?>) entry.getValue();}actualType = (TypeVariable<?>) entry.getValue();}}} catch (NoSuchFieldException e) { /* this should never happen */ } catch (IllegalAccessException e) { /* this might happen */}}throw new IllegalArgumentException();
}private static boolean isInnerClass(GenericDeclaration outerDeclaration, GenericDeclaration innerDeclaration) {if (!(outerDeclaration instanceof Class) || !(innerDeclaration instanceof Class)) {throw new IllegalArgumentException();}Class<?> outerClass = (Class<?>) outerDeclaration;Class<?> innerClass = (Class<?>) innerDeclaration;while ((innerClass = innerClass.getEnclosingClass()) != null) {if (innerClass == outerClass) {return true;}}return false;
}

哇,真丑! 但是上面的代码使findSubClassParameterType甚至可以用于嵌套类。 我们可以进一步详细介绍,因为我们还可以找到通用接口,通用方法,字段或数组的类型。 但是,所有此类提取的想法都相同。 如果子类知道其父类的泛型参数,则可以通过反射来获取它们。 否则,由于类型擦除,泛型参数将在运行时丢失而无法挽回。

但是最后,这有什么好处? 对于许多开发人员而言,这传达了执行黑魔法的印象,因此他们宁愿避免编写此类代码。 诚然,通常有更简单的方法来执行这种查询。 我们可以这样定义MyGenericSubclass:

class MyGenericClass<T> {private final Class<T> clazz;public MyGenericClass(Class<T> clazz) {this.clazz = clazz;}public Class<T> getGenericClass() {return clazz;}
}

当然,这也很好,甚至是更少的代码。 但是,当您编写供其他开发人员使用的API时,您通常希望它们尽可能的小巧和简单。 (这可以从编写大型框架到以两个为一组的形式编写软件。)通过上述实现,您可以迫使班级用户提供可以通过不同方式检索的冗余信息。 同样,这种方法对于您暗中要求实现类添加相应构造函数的接口也不太可行。 当着眼于Java 8及其功能接口 (也称为闭包或lambda表达式)时,此问题将变得更加重要。 如果您需要通用接口除了提供其功能方法之外还提供getGenericClass方法,则不能再在lambda表达式中使用它们。

PS:在写这篇博客文章时,我破解了此代码,但从未真正测试过,而是通过dupa调试 。 如果您需要这种功能,那么可以使用一个出色的名为gentyref的库,它提供了上述分析以及更多内容。

参考: 高级Java泛型:从My每日Java博客中的JCG合作伙伴 Rafael Winterhalter 检索泛型类型参数 。

翻译自: https://www.javacodegeeks.com/2013/12/advanced-java-generics-retreiving-generic-type-arguments.html

java实现泛型检索

java实现泛型检索_高级Java泛型:检索泛型类型参数相关推荐

  1. epub java虚拟机精讲_高级 Java 必须掌握:JVM 分析工具和查看命令,超详细!

    来源:http://boendev.iteye.com/blog/882479 jinfo 可以输出并修改运行时的java 进程的opts. jps 与unix上的ps类似,用来显示本地的java进程 ...

  2. java项目----教务管理系统_基于Java的教务管理系统

    java项目----教务管理系统_基于Java的教务管理系统 2022-04-22 18:18·java基础 最近为客户开发了一套学校用教务管理系统,主要实现学生.课程.老师.选课等相关的信息化管理功 ...

  3. Java培训课程 - 让训练有素的高级java课程通过专家

    Java培训课程 - 让训练有素的高级java课程通过专家 由Sun Microsystems公司开发的Java是最流行的编程语言,由于其在网络,移动,桌面应用程序开发广泛使用的.随着大量在印度研究院 ...

  4. Java的泛型特性_学习Java的重中之重!

    Java的"泛型"特性,你以为自己会了 使用Java的小伙伴,对于Java的一些高级特性一定再熟悉不过了,例如集合.反射.泛型.注解等等,这些可以说我们在平时开发中是经常使用到的, ...

  5. java泛型 例子_关于 Java 泛型的一些有趣的例子

    有以下的代码: 1 try{2 ArrayList lstA = new ArrayList();3 ArrayList lstB = new ArrayList();4 5 //ArrayList ...

  6. java高级教程_高级Java教程

    java高级教程 课程大纲 学习Java基础很容易. 但是,真正钻研该语言并研究其更高级的概念和细微差别将使您成为一名出色的Java开发人员. 网络上充斥着"软","便宜 ...

  7. 北京java研发平均工资_各地java开发工程师平均工资 北京高级java开发工程师工资2万多...

    原标题:各地java开发工程师平均工资 北京高级java开发工程师工资2万多 java开发工程师工资水平一直都是程序员们比较关注的话题,毕竟java是最热门的编程的语言.每个城市的由于经济水平的差距, ...

  8. java是什么格式_是java格式

    错误:编码GBK的不可映射字符的解决办法 最近在重新补javaSE的基础,编辑器编写完代码以后,在控制台运行代码段的时候,出现了以下的错误提示:错误:编码GBK的不可映射字符 在通过查询谷哥和度娘以后 ...

  9. java 1.6 最大化_关于java:JDK 1.6和1.7中的新功能

    我很了解JDK 1.5.众所周知,除了新的API(例如AtomicInteger),还有一些主要的语言更改,例如提供对泛型的支持.添加枚举.私有化自动装箱和自动取消装箱等. 我想找到一些资源,它们最好 ...

最新文章

  1. 嵌入式系统理解及相关知识100问
  2. 邬贺铨:工业物联网的技术与前景
  3. [机器学习]回归--Polinomial Regression 多项式回归
  4. basler相机的触发线是那两脚_车牌相机是怎么和LED屏联动的?
  5. Say Hello to Integers (Java)
  6. Win 10 忘记密码不用U盘就可解决
  7. iOS获取高德地图实现持续定位功能
  8. vivo和OPPO兄弟相煎从国内延伸至东南亚市场
  9. 历史性一刻,中国航天器首次登上火星!!!
  10. Visual Studio Code 是啥?
  11. 笔记 -- 电磁学基础2
  12. CMake是什么?有什么用?
  13. 全局变量和静态变量以及内存堆栈的关系
  14. 五分钟教会你如何使用GitHub
  15. UWB 厘米级定位技术全新升级汽车数字钥匙,NXP NCJ29D5 将助力手机无感进出
  16. 家谱管理系统c++实现
  17. 【办公】PPT制作流程
  18. 梳理半月有余,精心准备了17张知识思维导图,这次要讲清统计学
  19. 如何选择一款好的护眼台灯?2022性价比最高的护眼台灯推荐
  20. mysql 查询条件不区分大小写

热门文章

  1. 各种模板(数据结构图论)
  2. Spark Streaming之统计socket单词数
  3. Google的面试题长啥样
  4. Spring BeanFactory 容器
  5. 老师 累了,我们 泪了!
  6. Mybatis+mysql动态分页查询数据案例——房屋信息的接口(IHouseDao)
  7. JS中闭包的应用自定义JS模块2
  8. 检测性异常VS非检测性异常
  9. WIN10的IE错误代码inet_e_resource_not_found解决办法
  10. php 去掉url中的index.php,php 去掉url中的index.php