站长资讯网
最全最丰富的资讯网站

一起来分析Java泛型和泛型的通配符

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于泛型以及泛型的通配符相关问题,因为泛型的支持是编译器支持,字节码加载到虚拟机的时候泛型信息已经被擦除,所以泛型不支持一些运行时特性,下面一起来看一下,希望对大家有帮助。

一起来分析Java泛型和泛型的通配符

程序员必备接口测试调试工具:立即使用
Apipost = Postman + Swagger + Mock + Jmeter
Api设计、调试、文档、自动化测试工具
后端、前端、测试,同时在线协作,内容实时同步

推荐学习:《java视频教程》

泛型不是运行时特性

我们这里依然说的是Open JDK

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

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

new Plate(...) new Plate(...) class Plate {     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}.     * @param converterType the selected converter type     * @return whether this interceptor should be invoked or not     */    boolean supports(MethodParameter methodParameter, Type targetType,          Class> converterType);    ... }
登录后复制

在ping Webmvc中,RequestBodyAdvice用来处理http请求的body,supports用来判断是否支持某种参数类型到HttpMessage请求的转换。

HttpMessageConverter是一个接口,比如支持Body为Json格式的JsonViewRequestBodyAdvice类,实现如下:

@Override public boolean supports(MethodParameter methodParameter, Type targetType,       Class> 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> 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 p = new Plate(new Apple());         p.set(new Apple());//可以set           Apple apple = p.get();                    Plate q = new Plate(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 p = new Plate(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 implements ListenableFuture {   ...    @Override    public void addCallback(ListenableFutureCallback callback) {       this.settableTask.addCallback(callback);    }    @Override    public void addCallback(SuccessCallback 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 callback) {    try {       callback.onSuccess((T) this.result);    }    catch (Throwable ex) {       // Ignore    } }
登录后复制

SuccessCallback是一个函数式接口,从设计模式的角度来看是一个消费者,消费<T>类型的result。ListenableFutureCallback同理。

public interface SuccessCallback {    /**     * Called when the {@link ListenableFuture} completes with success.     * 

Note that Exceptions raised by this method are ignored. * @param result the result */ void onSuccess(@Nullable T result); }

登录后复制

为什么要用notifySuccess(SuccessCallback<? super T> callback)呢?

这是因为super能支持的范围

赞(0)
分享到: 更多 (0)