高级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的所有实例仍可以共享一个类表示形式,因为为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

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

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

相关文章

php 算法

<? //-------------------- // 基本数据结构算法 //-------------------- //二分查找&#xff08;数组里查找某个元素&#xff09; function bin_sch($array, $low, $high, $k){ if ( $low < $high){ $mid intval(($low$high)/2 ); if…

python利用opencv去除图片logo_利用python和opencv批量去掉图片黑边

import osimport cv2import numpy as npfrom scipy.stats import modeimport timeimport concurrent.futures‘‘‘multi-process to crop pictures.‘‘‘def crop(file_path_list):origin_path, save_path file_path_listimg cv2.imread(origin_path)gray cv2.cvtColor(im…

angularJS解决数据显示闪一下的问题?-解决办法

转自&#xff1a;https://www.cnblogs.com/e0yu/p/7219930.html?utm_sourceitdadao&utm_mediumreferral#undefined 使用 angular JS 的时候&#xff0c;把 angularJS 放到文件底部&#xff0c;在渲染页面的时候&#xff0c;会出现闪一下的情况&#xff1a; 解决办法一&a…

vue的钩子函数

1.computed 计算属性 计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 1..aPlus: {get: function () {return this.a 1},set: function (v) {this.a v - 1}}2.. aPlus(){    return this.$router.params  } 这两种方法都可以&am…

python英文字符串排序_Python根据内嵌的数字将字符串排序(sort by numbers embedded in strings)...

标签&#xff1a;import rere_digits re.compile(r‘(\d)‘)def embedded_numbers(s):pieces re_digits.split(s) # 切成数字与非数字pieces[1::2] map(int, pieces[1::2]) # 将数字部分转成整数return piecesdef sort_strings_with_embedded_numbers(ali…

定时运行python脚本并发送邮件_python实现定时发送邮件到指定邮箱

本文实例为大家分享了python实现定时发送邮件到指定邮箱的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下整个链路&#xff1a;传感器采集端采集数据&#xff0c;边缘端上传数据库&#xff0c;从数据库拿到数据。产品端有个自动出报告的需求&#xff0c;并且希望自动…

使用Guava的AbstractInvocationHandler正确完成代理

不太经常&#xff0c;但有时我们被迫使用java.lang.reflect.Proxy编写自定义动态代理类 。 这种机制的确没有魔力&#xff0c;而且即使您永远不会真正使用它&#xff0c;也值得知道-因为Java代理在各种框架和库中无处不在。 这个想法很简单&#xff1a;动态创建一个实现一个或…

php异常处理机制

转自&#xff1a;https://www.cnblogs.com/water0729/p/5802476.html php异常我们常接触到的就是error错误码1&#xff0c;warning错误码2&#xff0c;notice错误码8这三类。出现error了系统是挂掉了&#xff0c;但是warning和notice是我们可以捕捉并处理的 php配置项display_e…

JavaScript 事件处理详解

事件绑定与解绑&#xff1a; el.onEventName function (){} document.getElementById("dom").onclick function(){ } //绑定事件 document.getElementById("dom").onclick null; //移除绑定 dom0级事件&#xff0c;也就是最早期js处理事…

webbrowser设置横向打印_C# 日常记录:指定打印机/纸张/纸盒(静默打印)(不弹窗打印)WinForm篇...

我在WinForm 编程时一直有一个困扰很久的问题&#xff0c;有很多时候我们需要静默打印 或者不想使用默认的UI 进行打印设置 这个时候我的想法如下 1. 获取全部打印机 2.获取打印机能打什么样的纸3.将打印机设置传入并打印首选用到的打印机设置类System.Drawing.Printing.Printe…

mysql异步非阻塞方式_如何理解swoole异步非阻塞?

传统的apache2handler或php-fpm本质上都是短生命周期(请求后释放资源)的FastCGI运行模式. 请求来了,master进程会调用worker进程来处理,处理完后释放资源. 假设你在functions.php里定义了1000个函数,那么每次请求,都要重新定义一次,有一定的性能损失. 好处则是修改保存代码后,下…

IDEA中使用Maven

Maven的安装与使用 安装 1、下载&#xff0c;官网下载。 2、解压&#xff0c;存放路径中不可包含空格和中文。如&#xff1a;"E:\dev\workspace\maven\apache-maven-3.6.0" 3、配置本地仓库&#xff0c;进入 "conf/settings.xml" 中&#xff0c;在 setting…

Java应用程序中的内存泄漏和内存管理

Java平台最突出的功能之一是其自动内存管理。 许多人错误地将此功能转换为Java中没有内存泄漏 。 但是&#xff0c;事实并非如此&#xff0c;我给人的印象是&#xff0c;现代Java框架和基于Java的平台&#xff0c;尤其是Android平台&#xff0c;越来越与这种错误的假设相矛盾。…

js (jQuery)分组数据

function getobjArr (data) {var result [];data.HELMET.system 系统分类// console.log(data)$.each(data.HELMET, function (index_h, elem_h) {var h {id: index_h,name: elem_h,Projects: []}$(data.sonProjects).each(function (index_p, elem_p) {elem_p.AppCategory…

【前端组件】

下拉列表&#xff1a;https://harvesthq.github.io/chosen/#optgroup-support转载于:https://www.cnblogs.com/helww/p/9718396.html

python后台开发知识点_面试总结:鹅厂Linux后台开发面试笔试C++知识点参考笔记...

文章每周持续更新&#xff0c;各位的「三连」是对我最大的肯定。可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇)文章是由自己笔试面试腾讯的笔记整理而来&#xff0c;整理的时候又回顾了一遍&#xff0c;中间工作忙断断续续整理了半个月&#xf…

python用turtle画彩虹_Python利用turtle库绘制彩虹代码示例

语言&#xff1a;PythonIDE&#xff1a;Python.IDE需求做出彩虹效果颜色空间RGB模型&#xff1a;光的三原色&#xff0c;共同决定色相HSB/HSV模型&#xff1a;H色彩&#xff0c;S深浅&#xff0c;B饱和度&#xff0c;H决定色相需要将HSB模型转换为RGB模型代码示例&#xff1a;#…

MongoDB事实:商品硬件上每秒插入80000次以上

在尝试一些时间序列集合时&#xff0c;我需要一个大数据集来检查我们的聚合查询在增加数据负载的情况下不会成为瓶颈。 我们解决了5000万份文档&#xff0c;因为超出此数目我们仍然会考虑分片。 每次事件如下所示&#xff1a; {"_id" : ObjectId("5298a5a03b3…

scala-jdbc-scalike操作jdbc数据库

1, 引入maven依赖 <!-- 使用 sclaikeJDBC --><dependency><groupId>org.scalikejdbc</groupId><artifactId>scalikejdbc_2.11</artifactId><version>3.3.1</version></dependency><dependency><groupId>org…

day 17python 面对对象之继承

一&#xff1a;什么面向对象的继承&#xff1f; 比较官方的说法就是&#xff1a; 继承&#xff08;英语&#xff1a;inheritance&#xff09;是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B&#xff0c;就把这个A称为“B的子类别”&#xff0c;而把B称…