Heim  >  Artikel  >  Java  >  Lassen Sie uns gemeinsam Java-Generika und generische Platzhalter analysieren

Lassen Sie uns gemeinsam Java-Generika und generische Platzhalter analysieren

WBOY
WBOYnach vorne
2022-10-10 15:55:551908Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über Java, in dem hauptsächlich Probleme im Zusammenhang mit Generika und generischen Platzhaltern vorgestellt werden, da die Unterstützung von Generika vom Compiler unterstützt wird und der Bytecode zu diesem Zeitpunkt in die virtuelle Maschine geladen wird Die Informationen wurden gelöscht, daher unterstützen Generika einige Laufzeitfunktionen nicht. Ich hoffe, dass sie für alle hilfreich sind.

Lassen Sie uns gemeinsam Java-Generika und generische Platzhalter analysieren

Empfohlene Studie: „Java-Video-Tutorial

Generika sind keine Laufzeitfunktion

Wir sprechen hier immer noch von Open JDK

da die Unterstützung von Generika vom Compiler unterstützt wird, und der Bytecode schon Die in Generic geladenen Informationen wurden beim Ausführen einer virtuellen Maschine gelöscht, sodass Generics einige Laufzeitfunktionen nicht unterstützen. Beachten Sie daher, dass einige Schreibmethoden nicht kompiliert werden können, z. B. new.

Wie unten gezeigt, ist die Klasse Plate8742468051c85b06f0a0af9e3e506b5c eine Klasse mit Generika, wie unten gezeigt:

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;
    }
}

Das generische T kann nicht neu sein, da T ein Typ ist, der von der virtuellen Maschine nicht erkannt wird.

Generische Platzhalter

Es gibt drei Formen generischer Variablenausdrücke, die Platzhalter verwenden:

  • 9c1d0d596ee24bbdf0ffbf260c2f3f2b: C9c1d0d596ee24bbdf0ffbf260c2f3f2b von A

  • 04d82237e1b46a16a337cbfb82dc5df5? super B> , der Elementtyp in c ist ungewiss

  • Was genau bedeutet es und wie man es verwendet, schauen wir uns mal an~

  • Obergrenzen-Wildcard

Im Bereich der objektorientierten Programmierung denken wir, dass die Basisklasse Die Basis befindet sich auf der obersten Ebene. Aus Sicht des Vererbungsbaums steht die Object-Klasse ganz oben.

Also nennen wir diesen Ausdruck 71b63f436d94a50f17c5ed897161f1dd

71b63f436d94a50f17c5ed897161f1dd repräsentiert T oder einen beliebigen generischen Typ, der den T-Typ erbt. Schauen Sie sich zunächst das folgende Beispiel an wird in der HttpMessage-Anfrage unterstützt.

HttpMessageConverter ist eine Schnittstelle wie die JsonViewRequestBodyAdvice-Klasse, die Body im Json-Format unterstützt. Die Implementierung lautet wie folgt:

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);
   ...
}

Verwenden Sie AbstractJackson2HttpMessageConverter, um JsonView zu verarbeiten. Die Jackson2-Bibliothek ist eine der beliebtesten Java-JSON-Parsing-Bibliotheken HttpMessageConverter, der mit Springboot geliefert wird.

Verschiedene Benutzer können verschiedene Arten von Ratschlägen definieren, sodass viele Parametertypen wie XML unterstützt werden. Dann ist die Funktion von sping-webmvc flexibler und vielseitiger in verschiedene HttpInputMessage über verschiedene HttpMessageConverter umwandeln. Wie unten gezeigt, ruft

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

die passende Empfehlungsliste über getMatchingAdvice(parameter, RequestBodyAdvice.class) ab, durchläuft diese Liste und analysiert die Ratschläge, die den Parameter unterstützen, um eine Anfrage vom Typ HttpInputMessage zu erhalten. Der Ausdruck des oberen Platzhalters kann nicht mehr festgelegt werden

Der Ausdruck des vorherigen Platzhalters kann das generische Feld nicht mehr festlegen. Die eigentliche Bedeutung ist, dass der obere gebundene Platzhalter den festgelegten generischen Typ nicht ändern kann. Schauen wir uns diese Demo an.

@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;
}

Platee2f62cfe50f2c33ec89772cfcab89a6c Dieser Ausdruck bedeutet, dass bei der Java-Kompilierung nur der spezifische Typ von Fruit, Apple oder anderen Unterklassen bekannt ist Nachdem dem Gerät ein Wert in p zugewiesen wurde, wird die Platte nicht als „Apple“ markiert, sondern mit einem Platzhalter „CAP#1“ (was durch Dekompilieren des Bytecodes mit Javap schwerwiegend sein kann), um die Erfassung einer Frucht anzuzeigen oder eine Unterklasse von Fruit .

Aber unabhängig davon, ob es als Platzhalter geschrieben ist oder nicht, beziehen sich Generika schließlich auf einen bestimmten Typ, und der Compiler verwendet ein spezielles „CAP#1“, sodass wir dieses Feld nicht mehr zurücksetzen können, da es sonst als inkonsistent erscheint Fehler bei der Typkompilierung.

Aber diese Funktion behindert die Nutzung nicht. Das Framework verwendet das Obergrenzen-Wildcard-Paradigma, um eine flexible Erweiterung zu erreichen.

Platzhalter für die untere GrenzeAls nächstes schauen wir uns den Platzhalter für die untere Grenze an.

Sprachfalle

Wir tappen leicht in eine Verständnisfalle und denken, dass wir nur Fruit oder die Basisklasse von Fruit festlegen können. Tatsächlich können nur Fruit- und Fruit-Unterklassen festgelegt werden. Um dies zu sehen, schreiben wir einen Komponententest.

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

Beim Zugriff können Sie Klassen oder T speichern, die in T konvertiert werden können, also Klassen, die Obst oder Obst-Unterklassen festlegen können.

Aber Sie müssen bei der Verwendung ein Objekt als Referenz verwenden.

Der asynchrone Rückruf von spring-kafkaSchauen wir uns nun ein praktisches Beispiel an.

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视频教程

Das obige ist der detaillierte Inhalt vonLassen Sie uns gemeinsam Java-Generika und generische Platzhalter analysieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen