本篇文章給大家?guī)砹岁P(guān)于java的相關(guān)知識(shí),其中主要介紹了關(guān)于泛型以及泛型的通配符相關(guān)問題,因?yàn)榉盒偷闹С质蔷幾g器支持,字節(jié)碼加載到虛擬機(jī)的時(shí)候泛型信息已經(jīng)被擦除,所以泛型不支持一些運(yùn)行時(shí)特性,下面一起來看一下,希望對(duì)大家有幫助。
程序員必備接口測(cè)試調(diào)試工具:立即使用
Apipost = Postman + Swagger + Mock + Jmeter
Api設(shè)計(jì)、調(diào)試、文檔、自動(dòng)化測(cè)試工具
后端、前端、測(cè)試,同時(shí)在線協(xié)作,內(nèi)容實(shí)時(shí)同步
推薦學(xué)習(xí):《java視頻教程》
泛型不是運(yùn)行時(shí)特性
我們這里依然說的是Open JDK
因?yàn)榉盒偷闹С质蔷幾g器支持,字節(jié)碼加載到虛擬機(jī)的時(shí)候泛型信息已經(jīng)被擦除,所以泛型不支持一些運(yùn)行時(shí)特性。所以要注意有些寫法將編譯不過,比如new。
如下,類Plate<T>是帶泛型的類,如下演示,
new Plate(...) new Plate(...) class Plate { T item; public Plate(T t) { new T();//是錯(cuò)誤的,因?yàn)門是一個(gè)不被虛擬機(jī)所識(shí)別的類型,最終會(huì)被編譯器擦除轉(zhuǎn)為Object類給到虛擬機(jī) item = t; } public void set(T t) { item = t; } public T get() { return item; } }
泛型T不能被new,因?yàn)門是一個(gè)不被虛擬機(jī)所識(shí)別的類型。
泛型通配符
存在三種形式的用通配符的泛型變量表達(dá),分別是:
-
<? extends A>: C<? extends A> c,c中的元素類型都是A或者A的子類
-
<? super B>:C<? super B> c,c中的元素類型是B或者B的父類
-
<?>:C<?> c,c中的元素類型不確定
具體是什么意思以及怎么使用,我們一起來看看吧~
上界通配符
在面向?qū)ο缶幊填I(lǐng)域,我們認(rèn)為基類base在最上層。從繼承樹的角度來看,Object類處于最上層。
所以我們將這樣的表達(dá)<? extends T>稱為上界通配符。
<? extends T>表示T或繼承T類型的任意泛型類型。
先看下面這個(gè)例子.
Sping Webmvc中的RequestBodyAdvice
public interface RequestBodyAdvice { /** * Invoked first to determine if this interceptor applies. * @param methodParameter the method parameter * @param targetType the target type, not necessarily the same as the method * parameter type, e.g. for {@code HttpEntity}. * @param converterType the selected converter type * @return whether this interceptor should be invoked or not */ boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType); ... }
在ping Webmvc中,RequestBodyAdvice用來處理http請(qǐng)求的body,supports用來判斷是否支持某種參數(shù)類型到HttpMessage請(qǐng)求的轉(zhuǎn)換。
HttpMessageConverter是一個(gè)接口,比如支持Body為Json格式的JsonViewRequestBodyAdvice類,實(shí)現(xiàn)如下:
@Override public boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType) { return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) && methodParameter.getParameterAnnotation(JsonView.class) != null); }
使用AbstractJackson2HttpMessageConverter來處理JsonView,Jackson2庫(kù)是流行的Java JSON解析庫(kù)之一,也是Springboot自帶的HttpMessageConverter.
不同的使用方可以自己定義不同類型的Advice,便使得能支持非常多的參數(shù)類型比如xml,那么sping-webmvc的功能也就更加靈活通用了,可以將很多Type通過不同的HttpMessageConverter翻譯為不同的HttpInputMessage請(qǐng)求。如下所示,
@Override public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) throws IOException { for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) { if (advice.supports(parameter, targetType, converterType)) { request = advice.beforeBodyRead(request, parameter, targetType, converterType); } } return request; }
通過getMatchingAdvice(parameter, RequestBodyAdvice.class)獲得匹配的advice列表,遍歷這個(gè)列表解析支持parameter的Advice得到HttpInputMessage類型的請(qǐng)求。
上界通配符的表達(dá)無法再set
使用上屆通配符的表達(dá)方式無法再設(shè)置泛型字段,其實(shí)意思就是上界通配符不能改變已經(jīng)設(shè)置的泛型類型,我們一起來看下這個(gè)demo。
@Test void genericTest() { Platep = new Plate (new Apple()); p.set(new Apple());//可以set Apple apple = p.get(); Plate extends Fruit> q = new Plate (new Apple()); Fruit fruit = q.get(); q.set(new Fruit());//將編譯錯(cuò)誤 }
Plate<? extends Fruit>這種表達(dá)方式意味著java編譯期只知道容器里面存放的是Fruit和它的派生類,具體是什么類型不知道,可能是Fruit、Apple或者其他子類, 編譯器在p賦值以后,盤子里面沒有標(biāo)記為“Apple",只是標(biāo)記了一個(gè)占位符“CAP#1”(可以通過javap反編譯字節(jié)碼來嚴(yán)重),來表示捕獲一個(gè)Fruit或者Fruit的子類。
但是不管是不是通配符的寫法,泛型終究指的是一種具體的類型,而且被編譯器使用了特殊的“CAP#1”,所以我們無法再重新設(shè)置這個(gè)字段了,否則就會(huì)出現(xiàn)類型不一致的編譯錯(cuò)誤了。
但這個(gè)特點(diǎn)對(duì)于用法來說并沒有妨礙,框架使用上界通配符范型達(dá)到靈活擴(kuò)展的目的。
下界通配符
接下來我們一起看下下界通配符,<? super T>表示T或T父類的任意類型,下界的類型是T。
語(yǔ)言陷阱
我們?cè)诶斫馍先菀椎羧胍粋€(gè)陷阱,以為只可以設(shè)置Fruit或Fruit的基類。實(shí)際上Fruit和Fruit的子類才可以設(shè)置進(jìn)去,讓我們寫一個(gè)單元測(cè)試來看看。
@Test void genericSuperTest() { Plate super Fruit> p = new Plate(new Fruit()); p.set(new Apple()); //ok,存取的時(shí)候可以存任意可以轉(zhuǎn)為T的類或T p.set(new Object()); //not ok,無法 set Object Object object = p.get();//ok Fruit object = p.get();//not ok,super Fruit不是Fruit的子類 }
存取的時(shí)候可以存可以轉(zhuǎn)為T的類或T,也就是可以設(shè)置Fruit或Fruit子類的類。
但是使用的時(shí)候必須使用object來引用。
spring-kafka的異步回調(diào)
現(xiàn)在,讓我們看實(shí)際的一個(gè)例子。
SettableListenableFuture是spring 并發(fā)框架的一個(gè)類,繼承自Future<T>,我們知道Future表示異步執(zhí)行的結(jié)果,T表示返回結(jié)果的類型。ListenableFuture可以支持設(shè)置回調(diào)函數(shù),如果成功了怎么處理,如果異常又如何處理。
在spring-kafka包里使用了SettableListenableFuture來設(shè)置異步回調(diào)的結(jié)果,kafka客戶端調(diào)用 doSend發(fā)送消息到kafka隊(duì)列之后,我們可以異步的判斷是否發(fā)送成功。
public class SettableListenableFutureimplements ListenableFuture { ... @Override public void addCallback(ListenableFutureCallback super T> callback) { this.settableTask.addCallback(callback); } @Override public void addCallback(SuccessCallback super T> successCallback, FailureCallback failureCallback) { this.settableTask.addCallback(successCallback, failureCallback); } ...
SettableListenableFuture有重載的addCallback函數(shù),支持添加ListenableFutureCallback<? super T> callback和SuccessCallback<? super T> successCallback;當(dāng)調(diào)用的異步方法成功結(jié)束的時(shí)候使用notifySuccess來觸發(fā)onSuccess的執(zhí)行,這個(gè)時(shí)候?qū)?shí)際異步執(zhí)行的結(jié)果變成參數(shù)給callback調(diào)用。
private void notifySuccess(SuccessCallback super T> callback) { try { callback.onSuccess((T) this.result); } catch (Throwable ex) { // Ignore } }
SuccessCallback是一個(gè)函數(shù)式接口,從設(shè)計(jì)模式的角度來看是一個(gè)消費(fèi)者,消費(fèi)<T>類型的result。ListenableFutureCallback同理。
public interface SuccessCallback{ /** * Called when the {@link ListenableFuture} completes with success. * Note that Exceptions raised by this method are ignored. * @param result the result */ void onSuccess(@Nullable T result); }
為什么要用notifySuccess(SuccessCallback<? super T> callback)呢?
這是因?yàn)閟uper能支持的范圍