溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java泛型和泛型的通配符實例代碼分析

發(fā)布時間:2022-10-10 16:02:26 來源:億速云 閱讀:124 作者:iii 欄目:編程語言

這篇“Java泛型和泛型的通配符實例代碼分析”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java泛型和泛型的通配符實例代碼分析”文章吧。

泛型不是運行時特性

我們這里依然說的是Open JDK

因為泛型的支持是編譯器支持,字節(jié)碼加載到虛擬機的時候泛型信息已經(jīng)被擦除,所以泛型不支持一些運行時特性。所以要注意有些寫法將編譯不過,比如new。

如下,類Plate<T>是帶泛型的類,如下演示,

new Plate(...)
new Plate(...)
class Plate{
    T item;
    public Plate(T t) {
        new T();//是錯誤的,因為T是一個不被虛擬機所識別的類型,最終會被編譯器擦除轉(zhuǎn)為Object類給到虛擬機
        item = t;
    }
    public void set(T t) {
        item = t;
    }
    public T get() {
        return item;
    }
}

泛型T不能被new,因為T是一個不被虛擬機所識別的類型。

泛型通配符

存在三種形式的用通配符的泛型變量表達(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類型的任意泛型類型。

先看下面這個例子.

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請求的body,supports用來判斷是否支持某種參數(shù)類型到HttpMessage請求的轉(zhuǎn)換。

HttpMessageConverter是一個接口,比如支持Body為Json格式的JsonViewRequestBodyAdvice類,實現(xiàn)如下:

@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter> converterType) {
   return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
         methodParameter.getParameterAnnotation(JsonView.class) != null);
}

登錄后復(fù)制

使用AbstractJackson2HttpMessageConverter來處理JsonView,Jackson2庫是流行的Java JSON解析庫之一,也是Springboot自帶的HttpMessageConverter.

不同的使用方可以自己定義不同類型的Advice,便使得能支持非常多的參數(shù)類型比如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類型的請求。

上界通配符的表達(dá)無法再set

使用上屆通配符的表達(dá)方式無法再設(shè)置泛型字段,其實意思就是上界通配符不能改變已經(jīng)設(shè)置的泛型類型,我們一起來看下這個demo。

    @Test
    void genericTest() {
       
        Plate p = new Plate(new Apple());
        p.set(new Apple());//可以set
          Apple apple = p.get();
          
        Plate q = new Plate(new Apple());
       
        Fruit fruit = q.get();
      
         q.set(new Fruit());//將編譯錯誤
    }

Plate<? extends Fruit>這種表達(dá)方式意味著java編譯期只知道容器里面存放的是Fruit和它的派生類,具體是什么類型不知道,可能是Fruit、Apple或者其他子類, 編譯器在p賦值以后,盤子里面沒有標(biāo)記為“Apple",只是標(biāo)記了一個占位符“CAP#1”(可以通過javap反編譯字節(jié)碼來嚴(yán)重),來表示捕獲一個Fruit或者Fruit的子類。

但是不管是不是通配符的寫法,泛型終究指的是一種具體的類型,而且被編譯器使用了特殊的“CAP#1”,所以我們無法再重新設(shè)置這個字段了,否則就會出現(xiàn)類型不一致的編譯錯誤了。

但這個特點對于用法來說并沒有妨礙,框架使用上界通配符范型達(dá)到靈活擴展的目的。

下界通配符

接下來我們一起看下下界通配符,<? super T>表示T或T父類的任意類型,下界的類型是T。

語言陷阱

我們在理解上容易掉入一個陷阱,以為只可以設(shè)置Fruit或Fruit的基類。實際上Fruit和Fruit的子類才可以設(shè)置進(jìn)去,讓我們寫一個單元測試來看看。

@Test
void genericSuperTest() {
    Plate p = new Plate(new Fruit());
    p.set(new Apple()); //ok,存取的時候可以存任意可以轉(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的子類
}

存取的時候可以存可以轉(zhuǎn)為T的類或T,也就是可以設(shè)置Fruit或Fruit子類的類。

但是使用的時候必須使用object來引用。

spring-kafka的異步回調(diào)

現(xiàn)在,讓我們看實際的一個例子。

SettableListenableFuture是spring 并發(fā)框架的一個類,繼承自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隊列之后,我們可以異步的判斷是否發(fā)送成功。

public class SettableListenableFutureimplements ListenableFuture{
  ...
   @Override
   public void addCallback(ListenableFutureCallback callback) {
      this.settableTask.addCallback(callback);
   }
   @Override
   public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) {
      this.settableTask.addCallback(successCallback, failureCallback);
   }
 ...

SettableListenableFuture有重載的addCallback函數(shù),支持添加ListenableFutureCallback<? super T> callback和SuccessCallback<? super T> successCallback;當(dāng)調(diào)用的異步方法成功結(jié)束的時候使用notifySuccess來觸發(fā)onSuccess的執(zhí)行,這個時候?qū)嶋H異步執(zhí)行的結(jié)果變成參數(shù)給callback調(diào)用。

private void notifySuccess(SuccessCallback callback) {
   try {
      callback.onSuccess((T) this.result);
   }
   catch (Throwable ex) {
      // Ignore
   }
}

SuccessCallback是一個函數(shù)式接口,從設(shè)計模式的角度來看是一個消費者,消費<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)呢?

這是因為super能支持的范圍更多,雖然實際產(chǎn)生了某一個具體類型的結(jié)果,比如kafka的send函數(shù)產(chǎn)生的結(jié)果類型為SendResult,其他的客戶端可能使用其他的Result類型,但是不管是什么類型,我們在使用Spring的時候,可以對異步的結(jié)果統(tǒng)一使用Object來處理。

比如下面的這段代碼,雖然是針對kafka客戶端的。但對于其他的使用了Spring SettableListenableFuture的客戶端,我們也可以在addCallback函數(shù)里使用Object來統(tǒng)一處理異常。

 @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> listenableFuture) {
        listenableFuture.addCallback(
                new SuccessCallback() {
                    @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("{}發(fā)送到kafka異常", msg.getContentType() + msg.getId(), throwable.getCause());
                    }
                });
    }
}

聲明某個條件的任意類型?

比如 Collection<E>類的這個函數(shù),

@Override
public boolean removeAll(Collection collection) {
  return delegate().removeAll(collection);
}

Collection的removeAll函數(shù)移除原集合中的一些元素,因為最終使用equals函數(shù)比較要移除的元素是否在集合內(nèi),所以這個元素的類型并不在意。

我們再看一個例子,LoggerFactory

public class LoggerFactory {
    public static Logger getLogger(Class clazz) {
        return new Logger(clazz.getName());
    }
}

LoggerFactory可以為任意Class根據(jù)它的名字生成一個實例。

以上就是關(guān)于“Java泛型和泛型的通配符實例代碼分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI