Java如何获取List<String>中的String详解

网友投稿 274 2022-08-26


Java如何获取List<String>中的String详解

目录前言问题场景问题讨论解决方案本文总结

前言

在写这篇文章之前,我几乎没有思路去定义这个问题。只是知道,List是泛型,是接口List的实现,实例化以后只能存储String类型的对象,仅此而已!

提到泛型,每个java开发人员都比较熟悉。常见的List、Map等;另外,我们在进行工具类、公共包的开发时,也经常使用泛型实现规范化、模板化的目标。

问题场景

最近,在为新系统封装公共包时遇到了一个与泛型有关的问题。在这里,结合实际场景讨论一下。描述一下场景:封装MQ中间件,统一MQ的消息订阅与处理过程,统一MQ相关日志与监控。

解决思路还是比较简单的,整体由三个部分组成(如下图所示):

IMessageSub:通过接口定义了需要订阅的Topic、Tag、消费组,并提供消息处理入口;MessageQueueListener:实现了RocketMQ的接口MessageListenerConcurrently,是真正的消费者,负责消息消费处理。MqSubManager:负责完成IMessageSub Bean的发现(所有实现类均为Spring的Bean对象),并通过MessageQueueListener实现对MQ的最终订阅。

通过这个图,我们可以知道MessageQueueListener#consumeMessage负责接收消息,转换为指定类型后,交给IMessageSub#processMessage进行处理。IMessageSub是一个泛型接口,consumeMessage需要把消息转换为IMessageSub实例的所需实际类型(如下ConcreteMessageSub1示例的DemoMsg)。

public interface IMessageSub {

String getTopic();

String getTag();

String getConsumerGroup();

void processMessage(MqEvent mqEvent);

}

@Component

public class ConcreteMessageSub1 implements IMessageSub {

public String getTopic(){

return "TOPIC_TEST"

}

public String getTag(){

return "TAG_TEST";

}

public String getConsumerGroup(){

return "CID_TEST"

}

public void processMessage(MqEvent mqEvent){

// do something...

}

}

public class MessageQueueListener implements MessageListenerConcurrently {

private IMessageSub> messageSub;

public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {

}

}

每个MessageQueueListener对象持有一个IMessageSub的实例messageSub,可能是DemoMsg、DemoMsg2等等,如何才能确定对象MessageQueueListener#messageSub的实际类型呢?

问题讨论

我们知道,对于一个非泛型对象,只需要调用其getClass()方法就可以了。但是对于泛型对象,做同样的操作,结果却不是我们想要的。如下简单示例:

public static void main(String[] args) {

String str = "";

System.out.println(str.getClass().getName());

List list=new ArrayList<>();

System.out.println(list.getClass().getName());

}

输出结果:

java.lang.Stringjava.util.ArrayList

泛型对象list的输出结果是“java.util.ArrayList”,貌似跟String没有关系了,怎么回事儿呢?这就涉及到Java泛型的实现原理了。

Java的泛型是一种伪泛型,是通过类型擦除(Type Erasure)实现的参数化类型(Parameterized Type),也就是说把所操作的数据类型作为参数的一种语法。具体的历史背景就是:

Java在实现泛型机制时,为了避免新增泛型类型,直接把需要支持泛型的原始类型泛型化,比如:ArrayList变为ArrayList。

这就需要,Java能够实现具备向前、向后兼容性的泛型,也就是说以前使用原始类型的代码可以继续被泛型使用,现在的泛型也可以作为参数传递给原始类型的代码。

为了实现以上功能,Java 设计者将泛型完全作为了语法糖加入了新的语法中,也就是说泛型对于JVM来说是透明的,有泛型的和没有泛型的代码,通过编译器编译后所生成的二进制代码是完全相同的。编译器在编译过程中去除泛型的过程,被称作类型擦除。

泛型的参数化类型本质可以应用在类、接口、方法,于是就产生了泛型类、泛型接口、泛型方法,可以说极大提升了Java代码的灵活性。

考察大家一个小知识,我们天天使用或者见到泛型,如List,你知道它的各个组成部分叫什么名字吗?

List中的E称为类型参数变量,整个称为List泛型类型。List中的Integer称为实际类型参数,整个List称为参数化的类型ParameterizedType。

类型擦除确实保证了良好的兼容性,但是在很多场景下我们确实需要知道泛型对象的原始信息。比如“问题场景”中获取泛型接口实现类对象的实际类型参数。

虽然在编译期间编译器擦除了泛型,但是在字节码中仍然保留了与泛型有关的信息,这就使得我们可以通过反射来获取泛型擦除前的原始信息。为了表达泛型类型声明,Java提供了接口Type及其子类型。

通过这些API我们可以对泛型的原始信息了如指掌:

Class类:描述具体类型,比较常见,可通过getClass()获取。TypeVariable接口:描述类型变量(如:T extends Comparable super T> ),通过getClass().getTypeParameters()。WildcardType接口:描述通配符 (如:? super T )。ParameterizedType接口:描述泛型类或接口类型(如:Comparable super T>),可通过getClass().getGenericInterfaces()获取后筛选。GenericArrayType接口:描述泛型数组(如:T[])

解决方案

了解泛型的原理后,结合反射包提供的API,我们就很容易解决第一部分提出的问题了。

结合上面的示例,MessageQueueListener#messageSub是一个泛型接口对象,实际为IMessageSub泛型接口的实现类(如ConcreteMessageSub1),我们的目标就是获取ConcreteMessageSub1实现泛型接口时指定的实际类型参数DemoMsg。所以,需要按照以下步骤进行处理:

获取对象messageSub所属类(如:ConcreteMessageSub1)实现的接口列表;仅保留接口列表中的描述泛型接口的参数化类型对象(ParameterizedType);在参数化类型对象中获取类型为IMessageSub的描述对象,即我们需要的参数化类型对象;获取该参数化类型对象的第一个实际类型参数(接口声明只有一个泛型参数)。

用代码实现如下所示:

/**

* 获取消息执行器范性类型

*

* @return 类型

*/

private Type getExecutorGenericThttp://ype(IEventProcessor eventProcessor) {

try {

Optional typeOptional = Arrays.stream(eventProcessor.getClass().getGenericInterfaces())

.filter(type -> ParameterizedType.class.isAssignableFrom(type.getClass()))

.map(type -> (ParameterizedType)type)

.filter(parameterizedType -> IEventProcessor.class.getTypeName().equals(parameterizedType.getRawType().getTypeName()))

.map(ParameterizedType::getActualTypeArguments)

.filter(actualTypes -> actualTypes.length YMIsDH> 0)

.map(actualTypes -> actualTypes[0])

.findFirst();

return typeOptional.orElse(null);

} catch (Throwable cause) {

}

return null;

}

本文总结

依托于泛型提供的API,我们可以开发出灵活的工具及框架,也可以使我们的代码更加简洁高效。可以说,Java的泛型是一种“语法糖”。以复用性更强的方式来提高开发效率,帮助开发人员在编译阶段识别系统存在的安全隐患,以更强的约束力来保证代码的健壮性。

本来只想简单的介绍获取参数化类型的方式,可是当把问题展开的时候,才发现自己对泛型的体系认识不够,每天上下班路上一边学习,一边记录笔记。关于泛型,还有许多要去学习和了解的知识,大家一起进步。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:shell练习(实验 掌握基本shell命令的使用)
下一篇:python操作MongoDB(python操作mongodb实验总结)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~