您的位置: 翼速应用 > 业内知识 > Java > 正文

一起聊聊Java泛型与泛型通配符的相关知识

    本文是关于java泛型以及泛型的通配符的相关知识分享,因为泛型的支持是编译器支持,字节码加载到虚拟机的时候泛型信息已经被擦除,所以泛型不支持一些运行时特性,详细内容如下。


一起聊聊Java泛型与泛型通配符的相关知识


一起聊聊Java泛型与泛型通配符的相关知识


泛型不是运行时特性


我们这里依然说的是Open JDK


因为泛型的支持是编译器支持,字节码加载到虚拟机的时候泛型信息已经被擦除,所以泛型不支持一些运行时特性。所以要注意有些写法将编译不过,比如new。


如下,类Plate<T>是带泛型的类,如下演示,


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是一个不被虚拟机所识别的类型。


泛型通配符


存在三种形式的用通配符的泛型变量表达,分别是:


●  <? extends A>: C<? extends A> c,c中的元素类型都是A或者A的子类


●  <? super B>:C<? super B> c,c中的元素类型是B或者B的父类


●  <?>:C<?> c,c中的元素类型不确定


具体是什么意思以及怎么使用,我们一起来看看吧~


上界通配符


在面向对象编程领域,我们认为基类base在最上层。从继承树的角度来看,Object类处于最上层。


所以我们将这样的表达<? extends T>称为上界通配符。


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


Plate<? extends Fruit>这种表达方式意味着java编译期只知道容器里面存放的是Fruit和它的派生类,具体是什么类型不知道,可能是Fruit、Apple或者其他子类, 编译器在p赋值以后,盘子里面没有标记为“Apple",只是标记了一个占位符“CAP#1”(可以通过javap反编译字节码来严重),来表示捕获一个Fruit或者Fruit的子类。


但是不管是不是通配符的写法,泛型终究指的是一种具体的类型,而且被编译器使用了特殊的“CAP#1”,所以我们无法再重新设置这个字段了,否则就会出现类型不一致的编译错误了。


但这个特点对于用法来说并没有妨碍,框架使用上界通配符范型达到灵活扩展的目的。


下界通配符


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


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


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


@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根据它的名字生成一个实例。


以上就是关于Java泛型与泛型通配符的相关知识分享,翼速应用平台内有更多相关资讯,欢迎查阅!


我来说两句

0 条评论

推荐阅读

  • 响应式布局CSS媒体查询设备像素比介绍

    构建响应式网站布局最常见的是流体网格,灵活调整大小的站点布局技术,确保用户在使用的幕上获得完整的体验。响应式设计如何展示富媒体图像,可以通过以下几种方法。

    admin
  • 提升网站的性能快速加载的实用技巧

    网站速度很重要,快速加载的网站会带来更好的用户体验、更高的转化率、更多的参与度,而且在搜索引擎排名中也扮演重要角色,做SEO,网站硬件是起跑线,如果输在了起跑线,又怎么跟同行竞争。有许多方法可提升网站的性能,有一些技巧可以避免踩坑。

    admin
  • 织梦CMS TAG页找不到标签和实现彩色标签解决方法

    织梦cms是我们常见的网站程序系统的一款,在TAG标签中常常遇到的问题也很多。当我们点击 tags.php 页的某个标签的时候,有时会提示:“系统无此标签,可 能已经移除!” 但是我们检查程序后台,以及前台显示页面。这个标签确实存在,如果解决这个问题那?

    admin
  • HTML关于fieldset标签主要的作用

    在前端开发html页面中常用的标签很多,今天为大家带来的是关于HTML中fieldset标签主要的作用说明,根据技术分析HTML

    admin

精选专题