多平台统一管理软件接口,如何实现多平台统一管理软件接口
274
2022-08-26
Java如何获取List<String>中的String详解
目录前言问题场景问题讨论解决方案本文总结
前言
在写这篇文章之前,我几乎没有思路去定义这个问题。只是知道,List
提到泛型,每个java开发人员都比较熟悉。常见的List、Map
问题场景
最近,在为新系统封装公共包时遇到了一个与泛型有关的问题。在这里,结合实际场景讨论一下。描述一下场景:封装MQ中间件,统一MQ的消息订阅与处理过程,统一MQ相关日志与监控。
解决思路还是比较简单的,整体由三个部分组成(如下图所示):
IMessageSub:通过接口定义了需要订阅的Topic、Tag、消费组,并提供消息处理入口;MessageQueueListener:实现了RocketMQ的接口MessageListenerConcurrently,是真正的消费者,负责消息消费处理。MqSubManager:负责完成IMessageSub Bean的发现(所有实现类均为Spring的Bean对象),并通过MessageQueueListener实现对MQ的最终订阅。
通过这个图,我们可以知道MessageQueueListener#consumeMessage负责接收消息,转换为指定类型后,交给IMessageSub#processMessage进行处理。IMessageSub
public interface IMessageSub
String getTopic();
String getTag();
String getConsumerGroup();
void processMessage(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
// do something...
}
}
public class MessageQueueListener implements MessageListenerConcurrently {
private IMessageSub> messageSub;
public ConsumeConcurrentlyStatus consumeMessage(List
}
}
每个MessageQueueListener对象持有一个IMessageSub的实例messageSub,可能是DemoMsg、DemoMsg2等等,如何才能确定对象MessageQueueListener#messageSub的实际类型呢?
问题讨论
我们知道,对于一个非泛型对象,只需要调用其getClass()方法就可以了。但是对于泛型对象,做同样的操作,结果却不是我们想要的。如下简单示例:
public static void main(String[] args) {
String str = "";
System.out.println(str.getClass().getName());
List
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
类型擦除确实保证了良好的兼容性,但是在很多场景下我们确实需要知道泛型对象的原始信息。比如“问题场景”中获取泛型接口实现类对象的实际类型参数。
虽然在编译期间编译器擦除了泛型,但是在字节码中仍然保留了与泛型有关的信息,这就使得我们可以通过反射来获取泛型擦除前的原始信息。为了表达泛型类型声明,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
获取对象messageSub所属类(如:ConcreteMessageSub1)实现的接口列表;仅保留接口列表中的描述泛型接口的参数化类型对象(ParameterizedType);在参数化类型对象中获取类型为IMessageSub的描述对象,即我们需要的参数化类型对象;获取该参数化类型对象的第一个实际类型参数(接口声明只有一个泛型参数)。
用代码实现如下所示:
/**
* 获取消息执行器范性类型
*
* @return 类型
*/
private Type getExecutorGenericThttp://ype(IEventProcessor eventProcessor) {
try {
Optional
.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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~