在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的所有实例仍可以共享一个类表示形式,因为为MyStringSubClass的所有实例设置了T = String。

但是,如何获取存储在字节码中的信息呢? 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 My Java Java博客上,从我们的JCG合作伙伴 Rafael Winterhalter 检索泛型类型参数 。

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

高级Java泛型:检索泛型类型参数相关推荐

  1. java实现泛型检索_高级Java泛型:检索泛型类型参数

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

  2. Java:反射+泛型:获取类型参数的实例

    2019独角兽企业重金招聘Python工程师标准>>> 场景描述: 我需要开发四个页面,每个页面都是只涉及增删改查的基本逻辑. 最简单的写法: 创建四个接口A,B,C,D,每个接口中 ...

  3. java 泛型service_java泛型

    什么时java泛型? 泛型时jdk1.5版本以后出现的一种对类.方法.接口的一种类型的约束,这种类型的约束是存在与编译时期的一种约束,在程序的运行时期是没有泛型的运用. 泛型的作用只存在与代码的编译时 ...

  4. Java多态与泛型 ,动态绑定,静态绑定

    文章目录 (一)多态 1.概念 1.1 使用继承: 1.2 实现接口(推荐): 2.作用 3.多态的实现原理 3.1 Java 动态绑定以及内部实现机制 程序绑定的概念 关于final.static. ...

  5. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  6. java泛型的泛型_Java 泛型总结(一):基本用法与类型擦除

    简介 Java 在 1.5 引入了泛型机制,泛型本质是参数化类型,也就是说变量的类型是一个参数,在使用时再指定为具体类型.泛型可以用于类.接口.方法,通过使用泛型可以使代码更简单.安全.然而 Java ...

  7. 14. Java基础之泛型

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...

  8. 9.1-全栈Java笔记: 容器泛型—认识Collection接口

    开发和学习中需要时刻和数据打交道,如果组织这些数据是我们编程中重要的内容. 我们一般通过"容器"来容纳和管理数据.   事实上,数组就是一种容器,可以在其中放置对象或基本类型数据. ...

  9. java中的泛型的使用与理解

    什么是泛型? 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写 体验泛型 代码时定义一些可变部份,那些部份在使用前必须作出指明.各种程序设计语言和其编译器.运行环境对泛型的支持均不一 ...

最新文章

  1. automation服务器不能创建对象”的问题的解决方案总结大全
  2. Serial Old收集器
  3. 计算机视觉结课论文,计算机视觉与图像识别结课论文
  4. java semaphore 等待时间_一个java同步工具类Semaphore的详解
  5. 手机影音第十三天,xutils3、Glide的使用获取网络图片;下拉、上滑刷新;缓存网络资源...
  6. [译]R语言——Shiny框架之入门(三):如何启动一个Shiny应用
  7. java文件读写 教程_Java对文件的读写操作(图文详解)
  8. DataType--数值类型
  9. Linux上查找最大文件的 3 种方法
  10. 认知系列3: 看看资深研发工程师的思维模式
  11. Fins源代码欧姆龙通讯 OneApiConnect
  12. 华为2022硬件工程师招聘全程经验
  13. 一些名人语录(不断添加中...)
  14. InoReader——网页无法打开
  15. mmclassification使用步骤与心得/ACCV实验记录
  16. 判断位数为3的高级写法
  17. 数据校验validation
  18. Rancher hosted Kubernetes AKS
  19. 【Android】studio查看日志解决刷爆问题
  20. 预测、迭代与优化:用AI探寻组合优化问题最优解

热门文章

  1. javabean与json转换(fastjson与jackson两个版本)
  2. redis集群信息分析-cluster info-cluster nodes
  3. aws lambda_API网关和AWS Lambda进行身份验证
  4. java中的jpa_在JPA中处理Java的LocalDateTime
  5. kafka streams_Kafka REST Proxy for MapR Streams入门
  6. mysql查询会话池_用户会话,数据控件和AM池
  7. Java 8中的StringJoiner与String.join的示例
  8. ejb生命周期_EJB 3.x:生命周期和并发模型(第1部分)
  9. 使用Spring Data Cassandra缓存的预备语句
  10. 使用混合多云每个人都应避免的3个陷阱(第3部分)