程式設計師必備介面測試偵錯工具:
推薦學習:《》
我們這裡依然說的是Open JDK
因為泛型的支援是編譯器支援,位元組碼載入到虛擬機器器的時候泛型資訊已經被擦除,所以泛型不支援一些執行時特性。所以要注意有些寫法將編譯不過,比如new。
如下,類Plate<T>是帶泛型的類,如下演示,
new Plate(...) new Plate<T>(...) class Plate<T> { T item; public Plate(T t) { new T();//是錯誤的,因為T是一個不被虛擬機器器所識別的型別,最終會被編譯器擦除轉為Object類給到虛擬機器器 item = t; } public void set(T t) { item = t; } public T get() { return item; } }
泛型T不能被new,因為T是一個不被虛擬機器器所識別的型別。
存在三種形式的用萬用字元的泛型變數表達,分別是:
<? extends A>: C<? extends A> c,c中的元素型別都是A或者A的子類
<? super B>:C<? super B> c,c中的元素型別是B或者B的父類別
<?>:C<?> c,c中的元素型別不確定
具體是什麼意思以及怎麼使用,我們一起來看看吧~
在物件導向程式設計領域,我們認為基礎類別base在最上層。從繼承樹的角度來看,Object類處於最上層。
所以我們將這樣的表達<? extends T>稱為上界萬用字元。
<? extends T>表示T或繼承T型別的任意泛型型別。
先看下面這個例子.
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<String>}. * @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請求的body,supports用來判斷是否支援某種引數型別到HttpMessage請求的轉換。
HttpMessageConverter是一個介面,比如支援Body為Json格式的JsonViewRequestBodyAdvice類,實現如下:
@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庫是流行的Java JSON解析庫之一,也是Springboot自帶的HttpMessageConverter.
不同的使用方可以自己定義不同型別的Advice,便使得能支援非常多的引數型別比如xml,那麼sping-webmvc的功能也就更加靈活通用了,可以將很多Type通過不同的HttpMessageConverter翻譯為不同的HttpInputMessage請求。如下所示,
@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列表,遍歷這個列表解析支援parameter的Advice得到HttpInputMessage型別的請求。
上界萬用字元的表達無法再set
使用上屆萬用字元的表達方式無法再設定泛型欄位,其實意思就是上界萬用字元不能改變已經設定的泛型型別,我們一起來看下這個demo。
@Test void genericTest() { Plate<Apple> p = new Plate<Apple>(new Apple()); p.set(new Apple());//可以set Apple apple = p.get(); Plate<? extends Fruit> q = new Plate<Apple>(new Apple()); Fruit fruit = q.get(); q.set(new Fruit());//將編譯錯誤 }
Plate<? extends Fruit>這種表達方式意味著java編譯期只知道容器裡面存放的是Fruit和它的派生類,具體是什麼型別不知道,可能是Fruit、Apple或者其他子類, 編譯器在p賦值以後,盤子裡面沒有標記為「Apple",只是標記了一個預留位置「CAP#1」(可以通過javap反編譯位元組碼來嚴重),來表示捕獲一個Fruit或者Fruit的子類。
但是不管是不是萬用字元的寫法,泛型終究指的是一種具體的型別,而且被編譯器使用了特殊的「CAP#1」,所以我們無法再重新設定這個欄位了,否則就會出現型別不一致的編譯錯誤了。
但這個特點對於用法來說並沒有妨礙,框架使用上界萬用字元範型達到靈活擴充套件的目的。
接下來我們一起看下下界萬用字元,<? super T>表示T或T父類別的任意型別,下界的型別是T。
語言陷阱
我們在理解上容易掉入一個陷阱,以為只可以設定Fruit或Fruit的基礎類別。實際上Fruit和Fruit的子類才可以設定進去,讓我們寫一個單元測試來看看。
@Test void genericSuperTest() { Plate<? super Fruit> p = new Plate<Fruit>(new Fruit()); p.set(new Apple()); //ok,存取的時候可以存任意可以轉為T的類或T p.set(new Object()); //not ok,無法 set Object Object object = p.get();//ok Fruit object = p.get();//not ok,super Fruit不是Fruit的子類 }
存取的時候可以存可以轉為T的類或T,也就是可以設定Fruit或Fruit子類的類。
但是使用的時候必須使用object來參照。
spring-kafka的非同步回撥
現在,讓我們看實際的一個例子。
SettableListenableFuture是spring 並行框架的一個類,繼承自Future<T>,我們知道Future表示非同步執行的結果,T表示返回結果的型別。ListenableFuture可以支援設定回撥函數,如果成功了怎麼處理,如果異常又如何處理。
在spring-kafka包裡使用了SettableListenableFuture來設定非同步回撥的結果,kafka使用者端呼叫 doSend傳送訊息到kafka佇列之後,我們可以非同步的判斷是否傳送成功。
public class SettableListenableFuture<T> implements ListenableFuture<T> { ... @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函數,支援新增ListenableFutureCallback<? super T> callback和SuccessCallback<? super T> successCallback;當呼叫的非同步方法成功結束的時候使用notifySuccess來觸發onSuccess的執行,這個時候將實際非同步執行的結果變成引數給callback呼叫。
private void notifySuccess(SuccessCallback<? super T> callback) { try { callback.onSuccess((T) this.result); } catch (Throwable ex) { // Ignore } }
SuccessCallback是一個函數式介面,從設計模式的角度來看是一個消費者,消費<T>型別的result。ListenableFutureCallback同理。
public interface SuccessCallback<T> { /** * Called when the {@link ListenableFuture} completes with success. * <p>Note that Exceptions raised by this method are ignored. * @param result the result */ void onSuccess(@Nullable T result); }
為什麼要用notifySuccess(SuccessCallback<? super T> callback)呢?
這是因為super能支援的範圍更多,雖然實際產生了某一個具體型別的結果,比如kafka的send函數產生的結果型別為SendResult,其他的使用者端可能使用其他的Result型別,但是不管是什麼型別,我們在使用Spring的時候,可以對非同步的結果統一使用Object來處理。
比如下面的這段程式碼,雖然是針對kafka使用者端的。但對於其他的使用了Spring SettableListenableFuture的使用者端,我們也可以在addCallback函數裡使用Object來統一處理異常。
@SneakyThrows public int kafkaSendAndCallback(IMessage message) { String msg = new ObjectMapper().writeValueAsString(message); log.debug("msg is {}. ", msg); ListenableFuture send = kafkaTemplate.send("test", msg); addCallback(message, send); return 0; } private void addCallback(IMessage msg, ListenableFuture<SendResult<String, String>> listenableFuture) { listenableFuture.addCallback( new SuccessCallback<Object>() { @Override public void onSuccess(Object o) { log.info("success send object = " + msg.getContentType() + msg.getId()); } }, new FailureCallback() { @Override public void onFailure(Throwable throwable) { log.error("{}傳送到kafka異常", msg.getContentType() + msg.getId(), throwable.getCause()); } }); } }
比如 Collection<E>類的這個函數,
@Override public boolean removeAll(Collection<?> collection) { return delegate().removeAll(collection); }
Collection的removeAll函數移除原集合中的一些元素,因為最終使用equals函數比較要移除的元素是否在集合內,所以這個元素的型別並不在意。
我們再看一個例子,LoggerFactory
public class LoggerFactory { public static Logger getLogger(Class<?> clazz) { return new Logger(clazz.getName()); } }
LoggerFactory可以為任意Class根據它的名字生成一個範例。
PECS是producer extends consumer super的縮寫。
也是對我們上面的分析的一個總結
意思是extends用於生產者模式,而super用於消費者模式。
消費者模式:比如上面的callback結果是為了消費;這些結果被消費處理。
生產者模式:比如那些Converter,我們要處理特定格式的http請求,需要生產不同的轉換器Converter。
推薦學習:《》
以上就是一起來分析Java泛型和泛型的萬用字元的詳細內容,更多請關注TW511.COM其它相關文章!