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

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实现泛型检索

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/344943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【渝粤题库】陕西师范大学900013 心身疾病防治与心理健康

一、单项选择题 1.现实生活中人们有着切身体会&#xff0c;精神紧张, 心理压力大, 情绪不稳定、 焦虑、悲伤等容易使人 A注意力不集中 B学习和工作效率提高 C思维和判断能力变强 D 免疫力增加 2.神经、内分泌和免疫系统是人类精神活动或心理活动的 A 数学基础 B 生理基础 C 天文…

APIGEE – API网关简介

在本文中&#xff0c;我想简要介绍一下APIGEE。 APIGEE主要提供现成的以下功能作为api网关。 协议转换 与任何协议进行转换&#xff0c;包括SOAP&#xff0c;REST&#xff0c;XML二进制或自定义 交通管理 开箱即用的灵活&#xff0c;分布式配额管理&#xff0c;速率限制和峰…

国家开放大学2021春1110工程地质题目

教育 教育 试卷代号&#xff1a; 1110 2021年春季学期期末统一考试 工程地质 试题 2021年7月 一、单项选择题&#xff08;将正确答案的序号填入括号&#xff0c;每小题2分&#xff0c;共计36分&#xff09; 1.地球的内圈中厚度最小的圈层是( )。 A.地壳 B.地核 C.地幔 D.不能…

linux 7 nmcli modify,nmcli的网络配置

使用nmcli命令配置网络NetworkManager是管理和监控网络设置的守护进程&#xff0c;设备既就是网络接口&#xff0c;连接是对网络接口的配置&#xff0c;一个网络接口可以有多个连接配置&#xff0c;但同时只有一个连接配置生效。1 配置主机名CentOS6 之前主机配置文件&#xff…

【渝粤题库】广东开放大学 互联网金融 形成性考核

选择题 题目&#xff1a;互联网金融的本质是 答案&#xff1a; A、平台经济 B、双边市场经济 C、营销模式 D、多边市场经济 题目&#xff1a;大数据的特点不包括 答案&#xff1a; A、多样 B、高速 C、低速 D、海量 题目&#xff1a;借贷关系的达成是通过借贷双方在平台上直接接…

【渝粤题库】广东开放大学 公共部门人力资源管理 形成性考核

选择题 题目&#xff1a;人本管理是以人为中心的管理&#xff0c;视员工为组织发展的&#xff08;&#xff09;。 答案&#xff1a; A、“第二资源” B、“重要资源” C、“主要资源” D、“第一资源” 题目&#xff1a;在现代组织赖以存在和发展的5类资源中&#xff0c;&#…

【渝粤题库】广东开放大学 基础会计 形成性考核

选择题 题目&#xff1a;会计科目是对&#xff08; &#xff09;的具体内容进行分类核算的项目。 题目&#xff1a;下列各项中&#xff0c;属于所有者权益类科目的是&#xff08;&#xff09;。 题目&#xff1a;引起资产和负债同时减少的业务是&#xff08; &#xff09;。 题…

删除linux 软raid0,配置Linux软RAID0

配置Linux软RAID0[[email protected] ~]# mdadm -C -v /dev/md0 -l 0 -n 2 /dev/sdb /dev/sdc[[email protected] ~]# mdadm -C -v /dev/md0 -l 0 -n 2 /dev/sdb /dev/sdcmdadm: chunk size defaults to 512Kmdadm: /dev/sdb appears to be part of a raid array:levelraid0 de…

内存泄漏分析_调查内存泄漏第2部分–分析问题

内存泄漏分析这个小型系列的第一个博客介绍了如何创建一个非常泄漏的示例应用程序&#xff0c;以便我们可以研究解决服务器应用程序上基于堆的问题的技术。 它展示了Producer-Consumer模式的一个大问题&#xff0c;即消费者代码必须能够至少与生产者一样快&#xff08;甚至不是…

【渝粤题库】广东开放大学 应急管理 形成性考核

选择题 题目&#xff1a;&#xff08;&#xff09;是突发公共事件应急管理工作的最高行政领导机构。 题目&#xff1a;以下突发公共事件中&#xff0c;属于公共安全事件的是&#xff08;&#xff09;&#xff1f; 题目&#xff1a;我们赖以生活的价值是天生的&#xff0c;包括真…

c语言2164错误,美能达C203253353复印机常见错误故障代码介绍

《美能达C203253353复印机常见错误故障代码介绍》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《美能达C203253353复印机常见错误故障代码介绍(2页珍藏版)》请在人人文库网上搜索。1、美能达C203253353复印机常见错误故障代码介绍柯尼卡美能达C203/253/353复印机常见…

【渝粤教育】广东开放大学 电路分析基础 形成性考核 (59)

选择题 题目&#xff1a;电路如图所示&#xff0c;Ua&#xff08; &#xff09;V&#xff0c;Ub&#xff08; &#xff09;V 题目&#xff1a;实际电流源开路时&#xff0c;该电流源内部 题目&#xff1a;电压和电流的参考方向关联的含义是 题目&#xff1a;用万用表测量电阻时…

将Java Flight Recorder与OpenJDK 11结合使用

Java Flight Recorder&#xff08;JFR&#xff09;曾经是Oracle JDK的商业附加组件。 由于它是与Java Mission Control一起最近开源的&#xff0c;因此使用OpenJDK 11的每个人现在都可以使用此出色的工具免费对Java应用程序进行故障排除。 JFR以前是专有解决方案&#xff0c;对…

【渝粤教育】广东开放大学 计量经济学 形成性考核 (21)

选择题 题目&#xff1a;计量经济学基下列&#xff08; &#xff09;学科的分支学科。 题目&#xff1a;经济计量模型的因变量一定是&#xff08; &#xff09;。 题目&#xff1a;描述微观主体经济活动中变量关系的计量经济模型是&#xff08; &#xff09;。 题目&#xff1a…

根号n向下取整的c语言编程,【向上取整/向下取整】C语言向上或向下取整 函数...

C语言有以下几种取整方法&#xff1a;1、直接赋值给整数变量。如&#xff1a;int i 2.5; 或 i (int) 2.5;这种方法采用的是舍去小数部分2、C/C中的整数除法运算符“/”本身就有取整功能(int / int)&#xff0c;但是整数除法对负数的取整结果和使用的C编译器有关。3、使用floo…

【渝粤教育】广东开放大学 人际沟通实务 形成性考核 (49)

选择题 题目&#xff1a;很久以前&#xff0c;有一个放羊的孩子&#xff0c;由于顽皮&#xff0c;明明没有狼出现&#xff0c;他偏喊&#xff1a;“狼来了&#xff0c;狼来了!”村里的男女老少听到他的呼喊都赶来帮忙&#xff0c;结果发现上当了&#xff0c;都十分恼火。之后这…

【渝粤教育】广东开放大学 国际金融 形成性考核 (48)

选择题 题目&#xff1a; 汇率变化对经济的影响&#xff0c;表现在 ( )。 题目&#xff1a;中间汇率是指&#xff08; &#xff09;的平均数。 题目&#xff1a;20世纪70年代以后&#xff0c;各国外汇储备逐渐走向多元化。 题目&#xff1a;本币对外升值后&#xff0c;资本流出…

【渝粤教育】广东开放大学 客户服务管理 形成性考核 (39)

选择题 题目&#xff1a;客户关系管理&#xff08;Customer Relationship Management&#xff0c;CRM&#xff09;最早产生于&#xff08; &#xff09;&#xff0c;由美国的Gartner Group提出 题目&#xff1a;下列对CRM的基本特点的描述&#xff0c;错误的是&#xff08; &am…

jooq映射原理_JOOQ事实:从JPA批注到JOOQ表映射

jooq映射原理JOOQ是一个简洁的框架&#xff0c;它解决了我在使用高级动态过滤查询时遇到的一个长期问题。 尽管Hibernate和JPA附带了一个有用的Criteria API&#xff08;我已经使用了很长一段时间&#xff09;&#xff0c;但是您可以使用这些API进行操作有一些可以理解的限制。…

c语言开发工具程序代码是什么文件,【C语言】开发工具--GCC使用入门

来自&#xff1a; 51CTO GCC使用入门通常所说的GCC是GUN Compiler Collection的简称&#xff0c;除了编译程序之外&#xff0c;它还含其他相关工具&#xff0c;所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码。GCC是Linux平台下最常用的编…