Java ジェネリックとジェネリック ワイルドカードを一緒に分析しましょう

WBOY
リリース: 2022-10-10 15:55:55
転載
1971 人が閲覧しました

この記事では、java に関する関連知識を提供します。主にジェネリックとジェネリック ワイルドカードに関連する問題を紹介します。ジェネリックのサポートはコンパイラ サポートであるため、コードがロードされるとバイト ジェネリック情報が消去されます。仮想マシンなので、ジェネリックは一部のランタイム機能をサポートしていません。一緒に見てみましょう。皆さんのお役に立てれば幸いです。

Java ジェネリックとジェネリック ワイルドカードを一緒に分析しましょう

推奨される学習: 「Java ビデオ チュートリアル

ジェネリックはランタイム機能ではありません

まだありますここで私が話しているのは、Open JDKです。

ジェネリックサポートはコンパイラによってサポートされているため、バイトコードが仮想マシンにロードされるときにジェネリック情報が消去されているため、ジェネリックは一部のランタイム機能をサポートしていません。そのため、new などの一部の書き込みメソッドはコンパイルされないことに注意してください。

以下に示すように、クラス Plate は、ジェネリックスを備えたクラスです。

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 は、によって認識されない型であるため、ジェネリック T を新規にすることはできません。仮想マシン。

汎用ワイルドカード

ワイルドカード文字を使用した汎用変数表現には次の 3 つの形式があります:

  • ##: C c、c の要素タイプはすべて A または A のサブクラスです

  • ##:C c、c の要素タイプは B またはB
  • ##: C c、c の要素の型は不確実です
  • # を考えてみましょう。これが具体的に何を意味し、どのように使用するかを見てください~
上限ワイルドカード

オブジェクト指向プログラミングの分野では、基本クラスのベースが最上位にあると考えられています。継承ツリーの観点から見ると、Object クラスが最上位にあります。

したがって、この式 を上限ワイルドカードと呼びます。

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 リクエストの本文を処理する際、support は、特定のパラメータ タイプの HttpMessage リクエストへの変換がサポートされているかどうかを判断するために使用されます。

HttpMessageConverter は、Json 形式の Body をサポートする JsonViewRequestBodyAdvice クラスなどのインターフェイスです。実装は次のとおりです:

@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
   return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
         methodParameter.getParameterAnnotation(JsonView.class) != null);
}
ログイン後にコピー

Use AbstractJackson2HttpMessageConverter to process JsonView. Jackson2 ライブラリは、人気のあるライブラリの 1 つです。 Java JSON 解析ライブラリ。Springboot には HttpMessageConverter が付属しています。

さまざまなユーザーがさまざまなタイプのアドバイスを定義できるため、xml などの多くのパラメータ タイプをサポートでき、sping-webmvc の機能がより柔軟になります。さまざまな HttpMessageConverter を通じて、多くの Type をさまざまな 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) を通じて一致するアドバイス リストを取得し、このリストを走査し、パラメーターをサポートするアドバイスを解析し、タイプ HttpInputMessage のリクエストを取得します。

上限ワイルドカードの式は設定できなくなりました

以前のワイルドカードを使用した式は汎用フィールドを設定できなくなりました。実際には、上限のワイルドカードが設定されなくなったことを意味します。ワイルドカードは、設定されたジェネリック型を変更できません。このデモを見てみましょう。

    @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この式は、Java コンパイラがコンテナに Fruit とその派生クラスが含まれていることだけを認識していることを意味します。特定の型は不明です。Fruit、Apple、またはその他のサブクラスである可能性があります。コンパイラは p を割り当て、プレートは「Apple」としてマークされませんが、Fruit または Fruit のサブクラスのキャプチャを示すプレースホルダ「CAP#1」(Javap でバイトコードを逆コンパイルすると重大な問題になる可能性があります)でマークされます。 。

しかし、ワイルドカードとして記述されているかどうかに関係なく、ジェネリックは最終的に特定の型を参照し、コンパイラーは特別な「CAP#1」を使用するため、このフィールドをリセットすることはできなくなりました。一貫性のない型によるコンパイル エラーが発生します。

しかし、この機能は使用を妨げるものではなく、フレームワークは上限ワイルドカード パラダイムを使用して柔軟な拡張を実現します。

下限ワイルドカード

次に下限ワイルドカードを見てみましょう、 は 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のサブクラスを設定できるクラスを保存できます。

ただし、使用する場合は参照するオブジェクトを使用する必要があります。

spring-kafka の非同期コールバック

次に、実際の例を見てみましょう。

SettableListenableFuture是spring 并发框架的一个类,继承自Future,我们知道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 callback和SuccessCallback successCallback;当调用的异步方法成功结束的时候使用notifySuccess来触发onSuccess的执行,这个时候将实际异步执行的结果变成参数给callback调用。

private void notifySuccess(SuccessCallback<? super T> callback) {
   try {
      callback.onSuccess((T) this.result);
   }
   catch (Throwable ex) {
      // Ignore
   }
}
ログイン後にコピー

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

@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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート