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

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

WBOY
WBOY転載
2022-10-10 15:55:551956ブラウズ

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

汎用ワイルドカード

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

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

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

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

d203bb1ae585225d4838a2b7e3d0503eT または 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());//将编译错误
    }

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

しかし、ワイルドカードとして記述されているかどうかに関係なく、ジェネリックは最終的に特定の型を参照し、コンパイラーは特別な「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のサブクラスを設定できるクラスを保存できます。

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

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

声明:
この記事はjuejin.imで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。