Maison  >  Article  >  Java  >  Analysons ensemble les génériques Java et les caractères génériques génériques

Analysons ensemble les génériques Java et les caractères génériques génériques

WBOY
WBOYavant
2022-10-10 15:55:551909parcourir

Cet article vous apporte des connaissances pertinentes sur java, qui introduit principalement des problèmes liés aux génériques et aux jokers génériques, car la prise en charge des génériques est prise en charge par le compilateur et le bytecode est actuellement chargé dans la machine virtuelle. les informations ont été effacées, donc les génériques ne prennent pas en charge certaines fonctionnalités d'exécution. Jetons-y un coup d'œil, j'espère que cela sera utile à tout le monde.

Analysons ensemble les génériques Java et les caractères génériques génériques

Étude recommandée : "Tutoriel vidéo Java"

Les génériques ne sont pas une fonctionnalité d'exécution

Nous parlons toujours d'Open JDK ici

car le support des génériques est pris en charge par le compilateur, et le bytecode est Les informations génériques chargées ont été effacées lors de l'exécution d'une machine virtuelle. Les génériques ne prennent donc pas en charge certaines fonctionnalités d'exécution. Sachez donc que certaines méthodes d'écriture ne seront pas compilées, comme new.

Comme indiqué ci-dessous, la classe Plate8742468051c85b06f0a0af9e3e506b5c est une classe avec des génériques, comme indiqué ci-dessous,

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

Le T générique ne peut pas être nouveau, car T est un type qui n'est pas reconnu par la machine virtuelle.

Caractères génériques

Il existe trois formes d'expressions de variables génériques utilisant des caractères génériques, qui sont :

  • d1f234d60a222589a2788097410c6b19: Ca5d645d89a7daebd5fbffd9e2a4f88c3:C04d82237e1b46a16a337cbfb82dc5df5 c, le type d'élément dans c est B ou la classe parent de B

  • 6b3d0130bba23ae47fe2b8e8cddf0195:C261bf7117488d65922cf2c482b31fcc7

    d203bb1ae585225d4838a2b7e3d0503e représente T ou tout type générique qui hérite du type T.

    Regardez d'abord l'exemple suivant.

    RequestBodyAdvice dans Sping Webmvc

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

    Dans ping Webmvc, RequestBodyAdvice est utilisé pour traiter le corps de la requête http, et les supports sont utilisés pour déterminer si un certain type de paramètre est pris en charge dans la requête HttpMessage Convert.

    HttpMessageConverter est une interface, telle que la classe JsonViewRequestBodyAdvice qui prend en charge Body au format Json :

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

    Utilisez AbstractJackson2HttpMessageConverter pour traiter JsonView. HttpMessageConverter fourni avec Springboot.

    Différents utilisateurs peuvent définir eux-mêmes différents types de conseils, afin de pouvoir prendre en charge de nombreux types de paramètres tels que XML. La fonction de sping-webmvc sera alors plus flexible et polyvalente. traduit en différents HttpInputMessage via différentes demandes de HttpMessageConverters. Comme indiqué ci-dessous,

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

    obtient la liste de conseils correspondants via getMatchingAdvice(parameter, RequestBodyAdvice.class), parcourt cette liste, analyse les conseils qui prennent en charge le paramètre et obtient une requête de type HttpInputMessage.

    L'expression du caractère générique de limite supérieure ne peut plus être définie

    L'expression du caractère générique précédent ne peut plus définir le champ générique. La signification réelle est que le caractère générique de limite supérieure ne peut pas modifier le type générique défini. Jetons un coup d'oeil à cette démo.

        @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 Cette expression signifie que lors de la compilation Java, il sait seulement que Fruit et ses classes dérivées sont stockées dans le conteneur. Il peut s'agir de Fruit, Apple ou d'autres sous-classes. Une fois que l'appareil a reçu une valeur en p, la plaque n'est pas marquée comme "Apple", mais est marquée d'un espace réservé "CAP#1" (ce qui peut être sérieux en décompilant le bytecode avec javap) pour indiquer la capture d'un fruit. ou une sous-classe de Fruit .

    Mais peu importe qu'il soit écrit comme caractère générique ou non, les génériques font après tout référence à un type spécifique, et le compilateur utilise un "CAP#1" spécial, nous ne pouvons donc plus réinitialiser ce champ, sinon il apparaîtra incohérent. erreur de compilation de type.

    Mais cette fonctionnalité n'entrave pas l'utilisation. Le framework utilise le paradigme des caractères génériques de limite supérieure pour obtenir une expansion flexible.

    Caractère générique de limite inférieure

    Regardons ensuite le caractère générique de limite inférieure, 117c5a0bdb71ea9a9d0c2b99b03abe3e représente tout type de T ou de classe parent de T, et le type de limite inférieure est T.

    Piège linguistique

    Nous tombons facilement dans un piège de compréhension, pensant que nous ne pouvons définir que Fruit ou la classe de base de Fruit. En fait, seules les sous-classes Fruit et Fruit peuvent être définies. Écrivons un test unitaire pour voir.

    @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的子类
    }

    Lors de l'accès, vous pouvez enregistrer des classes ou T qui peuvent être converties en T, c'est-à-dire des classes qui peuvent définir des sous-classes de Fruits ou de Fruits.

    Mais vous devez utiliser un objet à référencer lorsque vous l'utilisez.

    rappel asynchrone de spring-kafka

    Maintenant, regardons un exemple pratique.

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer