Java如何獲取List<String>中的String詳解
前言
在寫這篇文章之前,我?guī)缀鯖]有思路去定義這個問題。只是知道,List<String>是泛型,是接口List<T>的實現(xiàn),實例化以后只能存儲String類型的對象,僅此而已!
提到泛型,每個Java開發(fā)人員都比較熟悉。常見的List、Map<K,V>等;另外,我們在進行工具類、公共包的開發(fā)時,也經(jīng)常使用泛型實現(xiàn)規(guī)范化、模板化的目標。
問題場景
最近,在為新系統(tǒng)封裝公共包時遇到了一個與泛型有關(guān)的問題。在這里,結(jié)合實際場景討論一下。描述一下場景:封裝MQ中間件,統(tǒng)一MQ的消息訂閱與處理過程,統(tǒng)一MQ相關(guān)日志與監(jiān)控。
解決思路還是比較簡單的,整體由三個部分組成(如下圖所示):
- IMessageSub:通過接口定義了需要訂閱的Topic、Tag、消費組,并提供消息處理入口;
- MessageQueueListener:實現(xiàn)了RocketMQ的接口MessageListenerConcurrently,是真正的消費者,負責(zé)消息消費處理。
- MqSubManager:負責(zé)完成IMessageSub Bean的發(fā)現(xiàn)(所有實現(xiàn)類均為Spring的Bean對象),并通過MessageQueueListener實現(xiàn)對MQ的最終訂閱。
通過這個圖,我們可以知道MessageQueueListener#consumeMessage負責(zé)接收消息,轉(zhuǎn)換為指定類型后,交給IMessageSub#processMessage進行處理。IMessageSub<T>是一個泛型接口,consumeMessage需要把消息轉(zhuǎn)換為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的實際類型呢?
問題討論
我們知道,對于一個非泛型對象,只需要調(diào)用其getClass()方法就可以了。但是對于泛型對象,做同樣的操作,結(jié)果卻不是我們想要的。如下簡單示例:
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()); }
輸出結(jié)果:
java.lang.String
java.util.ArrayList
泛型對象list的輸出結(jié)果是“java.util.ArrayList”,貌似跟String沒有關(guān)系了,怎么回事兒呢?這就涉及到Java泛型的實現(xiàn)原理了。
Java的泛型是一種偽泛型,是通過類型擦除(Type Erasure)實現(xiàn)的參數(shù)化類型(Parameterized Type),也就是說把所操作的數(shù)據(jù)類型作為參數(shù)的一種語法。具體的歷史背景就是:
Java在實現(xiàn)泛型機制時,為了避免新增泛型類型,直接把需要支持泛型的原始類型泛型化,比如:ArrayList變?yōu)锳rrayList。
這就需要,Java能夠?qū)崿F(xiàn)具備向前、向后兼容性的泛型,也就是說以前使用原始類型的代碼可以繼續(xù)被泛型使用,現(xiàn)在的泛型也可以作為參數(shù)傳遞給原始類型的代碼。
為了實現(xiàn)以上功能,Java 設(shè)計者將泛型完全作為了語法糖加入了新的語法中,也就是說泛型對于JVM來說是透明的,有泛型的和沒有泛型的代碼,通過編譯器編譯后所生成的二進制代碼是完全相同的。編譯器在編譯過程中去除泛型的過程,被稱作類型擦除。
泛型的參數(shù)化類型本質(zhì)可以應(yīng)用在類、接口、方法,于是就產(chǎn)生了泛型類、泛型接口、泛型方法,可以說極大提升了Java代碼的靈活性。
考察大家一個小知識,我們天天使用或者見到泛型,如List<E>,你知道它的各個組成部分叫什么名字嗎?
- List<E>中的E稱為類型參數(shù)變量,整個稱為List<E>泛型類型。
- List<Integer>中的Integer稱為實際類型參數(shù),整個List<Integer>稱為參數(shù)化的類型ParameterizedType。
類型擦除確實保證了良好的兼容性,但是在很多場景下我們確實需要知道泛型對象的原始信息。比如“問題場景”中獲取泛型接口實現(xiàn)類對象的實際類型參數(shù)。
雖然在編譯期間編譯器擦除了泛型,但是在字節(jié)碼中仍然保留了與泛型有關(guān)的信息,這就使得我們可以通過反射來獲取泛型擦除前的原始信息。為了表達泛型類型聲明,Java提供了接口Type及其子類型。
通過這些API我們可以對泛型的原始信息了如指掌:
- Class類:描述具體類型,比較常見,可通過getClass()獲取。
- TypeVariable接口:描述類型變量(如:T extends Comparable<? super T> ),通過getClass().getTypeParameters()。
- WildcardType接口:描述通配符 (如:? super T )。
- ParameterizedType接口:描述泛型類或接口類型(如:Comparable<? super T>),可通過getClass().getGenericInterfaces()獲取后篩選。
- GenericArrayType接口:描述泛型數(shù)組(如:T[])
解決方案
了解泛型的原理后,結(jié)合反射包提供的API,我們就很容易解決第一部分提出的問題了。
結(jié)合上面的示例,MessageQueueListener#messageSub是一個泛型接口對象,實際為IMessageSub<T>泛型接口的實現(xiàn)類(如ConcreteMessageSub1),我們的目標就是獲取ConcreteMessageSub1實現(xiàn)泛型接口時指定的實際類型參數(shù)DemoMsg。所以,需要按照以下步驟進行處理:
- 獲取對象messageSub所屬類(如:ConcreteMessageSub1)實現(xiàn)的接口列表;
- 僅保留接口列表中的描述泛型接口的參數(shù)化類型對象(ParameterizedType);
- 在參數(shù)化類型對象中獲取類型為IMessageSub的描述對象,即我們需要的參數(shù)化類型對象;
- 獲取該參數(shù)化類型對象的第一個實際類型參數(shù)(接口聲明只有一個泛型參數(shù))。
用代碼實現(xiàn)如下所示:
/** ?* 獲取消息執(zhí)行器范性類型 ?* ?* @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; }
本文總結(jié)
依托于泛型提供的API,我們可以開發(fā)出靈活的工具及框架,也可以使我們的代碼更加簡潔高效。可以說,Java的泛型是一種“語法糖”。以復(fù)用性更強的方式來提高開發(fā)效率,幫助開發(fā)人員在編譯階段識別系統(tǒng)存在的安全隱患,以更強的約束力來保證代碼的健壯性。
本來只想簡單的介紹獲取參數(shù)化類型的方式,可是當把問題展開的時候,才發(fā)現(xiàn)自己對泛型的體系認識不夠,每天上下班路上一邊學(xué)習(xí),一邊記錄筆記。關(guān)于泛型,還有許多要去學(xué)習(xí)和了解的知識,大家一起進步。
到此這篇關(guān)于Java如何獲取List<String>中的String的文章就介紹到這了,更多相關(guān)Java獲取List<String>中String內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合Redis實現(xiàn)高并發(fā)數(shù)據(jù)緩存的示例講解
這篇文章主要介紹了SpringBoot整合Redis實現(xiàn)高并發(fā)數(shù)據(jù)緩存,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03Java網(wǎng)絡(luò)編程TCP實現(xiàn)文件上傳功能
這篇文章主要為大家詳細介紹了Java網(wǎng)絡(luò)編程TCP實現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07Javaweb使用cors完成跨域ajax數(shù)據(jù)交互
本文由跨域、cors的概念開始,進而向大家介紹了Javaweb使用cors完成跨域ajax數(shù)據(jù)交互的相關(guān)內(nèi)容,需要的朋友可以了解下。2017-09-09JAVA中使用FTPClient實現(xiàn)文件上傳下載實例代碼
本文給大家介紹如何利用jakarta commons中的FTPClient(在commons-net包中)實現(xiàn)上傳下載文件。非常不錯具有參考借鑒價值,感興趣的朋友一起學(xué)習(xí)吧2016-06-06