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

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

前言

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

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

问题场景

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

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

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

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

public interface IMessageSub<T> {
 String getTopic();
    String getTag();
    String getConsumerGroup();
    void processMessage(MqEvent<DemoMsg> mqEvent);
}

@Component
public class ConcreteMessageSub1 implements IMessageSub<DemoMsg> {
 public String getTopic(){
     return "TOPIC_TEST"
    }
    
 public String getTag(){
     return "TAG_TEST";
    }
 public String getConsumerGroup(){
     return "CID_TEST"
    }
 public void processMessage(MqEvent<DemoMsg> mqEvent){
     // do something...
    }
}

public class MessageQueueListener implements MessageListenerConcurrently {

    private IMessageSub<?> messageSub;
    
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> 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<String> list=new ArrayList<>();
    System.out.println(list.getClass().getName());
}

输出结果:

java.lang.String
java.util.ArrayList

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

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

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

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

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

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

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

  • List<E>中的E称为类型参数变量,整个称为List<E>泛型类型。
  • List<Integer>中的Integer称为实际类型参数,整个List<Integer>称为参数化的类型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<T>泛型接口的实现类(如ConcreteMessageSub1),我们的目标就是获取ConcreteMessageSub1实现泛型接口时指定的实际类型参数DemoMsg。所以,需要按照以下步骤进行处理:

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

用代码实现如下所示:

/**
 * 获取消息执行器范性类型
 *
 * @return 类型
 */
private Type getExecutorGenericType(IEventProcessor eventProcessor) {
    try {
        Optional<Type> 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 > 0)
            .map(actualTypes -> actualTypes[0])
            .findFirst();
        return typeOptional.orElse(null);
    } catch (Throwable cause) {

    }
    return null;
}

本文总结

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

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

到此这篇关于Java如何获取List&lt;String&gt;中的String的文章就介绍到这了,更多相关Java获取List&lt;String&gt;中String内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java8 将一个List<T>转为Map<String,T>的操作

    将 List 转为 Map<String, T> public class AnswerApp { public static void main(String[] args) throws Exception { List<String> names = Lists.newArrayList("Answer", "AnswerAIL", "AI"); Map<String, Integer> map = na

  • java将String字符串转换为List<Long>类型实例方法

    在一些应用场景当中,我们可能会遇到以下的场景,我们要使用的类型是List类型,但是接收到的参数是Stirng类型如1,2,3,4等这样的形式 那么我们可以通过采用以下的代码完成以上需求的转换 private static Log log = LogFactory.getLog(Demo.class); @Test public void test() { String ids = "1, 3, 5, 7, 9"; // 首先去除空格 String idsWithNoBlank = id

  • Java Web Fragment在项目中使用方法详解

    Web Fragment 是什么 - 它是在 servlet 3.0开始支持的,可以把一个dy web项目拆分为多个项目,解耦合,使其在项目中开发效率提高,下面我演示简单的项目创建过程 用eclipse右键new->other->web->web fragment project 项目结构 web-fragment.xml 配置详细内容 <?xml version="1.0" encoding="UTF-8"?> <web-fra

  • Java 图文并茂讲解主方法中的String[] args参数作用

    目录 一.作用 二.在控制台传入参数 三.在IntelliJ IDEA中传入参数 总结 一.作用 主方法就是程序的入口,那么里面的String[] args参数是什么意思呢? String[]表示的是字符串类型的数组,args表示的是传入的参数名,所以整体的意思就是主方法main(String[] args)可以接收一个字符串类型的数组,数组名字为args.(相当于入参) args这个数组是留给用户的,用户可以在外部输入参数,这个参数会被自动转换为"Sting[] args"传入主方法

  • Java中String类常用方法使用详解

    目录 一.length() 二.equals 三.charAt() 四.indexOf() 五.trim() 六.compareTo() 七.toLowerCase() 八.toUpperCase() 九.replace() 十.substring(int beginIndex) 十一.substring(int beginIndex, int endIndex) 总结 一.length() 返回此字符串的长度 public static void main4(String[] args) {

  • Java中String类常用方法总结详解

    目录 一. String对象的比较 1. ==比较是否引用同一个对象 2. boolean equals(Object anObject) 3. int compareTo(String s) 4. int compareToIgnoreCase(String str) 二. 字符串查找 三. 转化 1. 数值和字符串转化 2. 大小写转化 3. 字符串和数组的转换 4. 格式化 四. 字符串替换 五. 字符串拆分 六. 字符串截取 七. 其他操作方法 1. String trim() 2. b

  • Java String 和StringBuffer的详解及区别

    Java String 和StringBuffer的详解及区别 Java平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含 多个字符的字符数据.String类表示内容不可改变的字符串.而StringBuffer类表示内 容可以被修改的字符串. 当你知道字符数据要改变的时候你就可以使用StringBuffer.典型地,你可以使用StringBuffers来动态构造 字符数据.另外,String实现了equals方法,new String("abc"

  • Java String对象使用方法详解

    Java String对象使用方法详解 先来看一个例子,代码如下: public class Test { public static void main(String[] args) { String str = "abc"; String str1 = "abc"; String str2 = new String("abc"); System.out.println(str == str1); System.out.println(str1

  • Java String 拼接字符串原理详解

    首先来一道思考题: String str1 = "111111"; String str2 = "222222"; String str = str1 + str2; System.out.println(str); 很明确,上述代码输出的结果是:"111111222222",但是它工作原理是怎样的呢? 由于字符串拼接太常用了,java才支持可以直接用+号对两个字符串进行拼接.**其真正实现的原理是中间通过建立临时的StringBuilder对象

  • Java中的HashSet详解和使用示例_动力节点Java学院整理

    第1部分 HashSet介绍 HashSet 简介 HashSet 是一个没有重复元素的集合. 它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素. HashSet是非同步的.如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步.这通常是通过对自然封装该 set 的对象执行同步操作来完成的.如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来"包装" set.

  • java 中cookie的详解及简单实例

    java 中cookie的详解 Java对cookie的操作比较简单,主要介绍下建立cookie和读取cookie,以及如何设定cookie的生命周期和cookie的路径问题. 建立一个无生命周期的cookie,即随着浏览器的关闭即消失的cookie,代码如下 HttpServletRequest request HttpServletResponse response Cookie cookie = new Cookie("cookiename","cookievalue&

  • Java 中的HashMap详解和使用示例_动力节点Java学院整理

    第1部分 HashMap介绍 HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.Serializable接口. HashMap 的实现不是同步的,这意味着它不是线程安全的.它的key.value都可以为null.此外,HashMap中的映射不是有序的. HashMap 的实例有两个参数影响其性能:"初始容量" 和 "加载因子&quo

随机推荐