首頁  >  文章  >  Java  >  一起來分析Java泛型和泛型的通配符

一起來分析Java泛型和泛型的通配符

WBOY
WBOY轉載
2022-10-10 15:55:551909瀏覽

本篇文章為大家帶來了關於java的相關知識,其中主要介紹了關於泛型以及泛型的通配符相關問題,因為泛型的支持是編譯器支持,字節碼載入到虛擬機器的時候泛型資訊已經被擦除,所以泛型不支援一些運行時特性,下面一起來看一下,希望對大家有幫助。

一起來分析Java泛型和泛型的通配符

推薦學習:《java影片教學

泛型不是執行時間特性

我們這裡依然說的是Open JDK

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

如下,類別Plate8742468051c85b06f0a0af9e3e506b5c是帶有泛型的類,如下演示,

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是一個不被虛擬機器所識別的類型。

泛型通配符

存在三種形式的用通配符的泛型變數表達,分別是:

  • d1f234d60a222589a2788097410c6b19: Cd1f234d60a222589a2788097410c6b19 c,c中的元素型別都是A或A的子類別

  • 04d82237e1b46a16a337cbfb82dc5df5:C04d82237e1b46a16a337cbfb82dc5df5 c,c中的元素型別是B或B的父類別

  • 6b3d0130bba23ae47fe2b8e8cddf0195:C6b3d0130bba23ae47fe2b8e8cddf0195 c,c中的元素型別不確定

##具體是什麼意思以及怎麼使用,我們一起來看看吧~

上界通配符

在物件導向程式設計領域,我們認為基底類別base在最上層。從繼承樹的角度來看,Object類別處於最上層。

所以我們將這樣的表達d203bb1ae585225d4838a2b7e3d0503e稱為上界通配符。

d203bb1ae585225d4838a2b7e3d0503e表示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());//将编译错误
    }

Plate57019040ccef885c8e3bd8f9deb31922這種表達方式意味著java編譯期只知道容器裡面存放的是Fruit和它的派生類,具體是什麼類型不知道,可能是Fruit、Apple或者其他子類, 編譯器在p賦值以後,盤子裡面沒有標記為“Apple",只是標記了一個佔位符“CAP#1”(可以通過javap反編譯字節碼來嚴重),來表示捕獲一個Fruit或者Fruit的子類別。

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

但這個特點對用法來說並沒有妨礙,框架使用上界通配符範式達到靈活擴展的目的。

下界通配符

接下來我們一起看下下界通配符,117c5a0bdb71ea9a9d0c2b99b03abe3e表示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 并发框架的一个类,继承自Future8742468051c85b06f0a0af9e3e506b5c,我们知道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函数,支持添加ListenableFutureCallback117c5a0bdb71ea9a9d0c2b99b03abe3e callback和SuccessCallback117c5a0bdb71ea9a9d0c2b99b03abe3e successCallback;当调用的异步方法成功结束的时候使用notifySuccess来触发onSuccess的执行,这个时候将实际异步执行的结果变成参数给callback调用。

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

SuccessCallback是一个函数式接口,从设计模式的角度来看是一个消费者,消费8742468051c85b06f0a0af9e3e506b5c类型的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(SuccessCallback117c5a0bdb71ea9a9d0c2b99b03abe3e 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());
                    }
                });
    }
}

声明某个条件的任意类型?

比如 Collection1a4db2c2c2313771e5742b6debf617a1类的这个函数,

@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

PECS是producer extends consumer super的缩写。

也是对我们上面的分析的一个总结

意思是extends用于生产者模式,而super用于消费者模式。

  • 消费者模式:比如上面的callback结果是为了消费;这些结果被消费处理。

  • 生产者模式:比如那些Converter,我们要处理特定格式的http请求,需要生产不同的转换器Converter。

推荐学习:《java视频教程

以上是一起來分析Java泛型和泛型的通配符的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除