本篇文章给大家带来了关于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不能被new,因为T是一个不被虚拟机所识别的类型。
存在三种形式的用通配符的泛型变量表达,分别是:
d1f234d60a222589a2788097410c6b19: Cd1f234d60a222589a2788097410c6b19 c,c中的元素类型都是A或者A的子类
04d82237e1b46a16a337cbfb82dc5df5:C04d82237e1b46a16a337cbfb82dc5df5 c,c中的元素类型是B或者B的父类
6b3d0130bba23ae47fe2b8e8cddf0195:C6b3d0130bba23ae47fe2b8e8cddf0195 c,c中的元素类型不确定
具体是什么意思以及怎么使用,我们一起来看看吧~
在面向对象编程领域,我们认为基类base在最上层。从继承树的角度来看,Object类处于最上层。
所以我们将这样的表达d203bb1ae585225d4838a2b7e3d0503e称为上界通配符。
d203bb1ae585225d4838a2b7e3d0503e表示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());//将编译错误 }
Plate57019040ccef885c8e3bd8f9deb31922这种表达方式意味着java编译期只知道容器里面存放的是Fruit和它的派生类,具体是什么类型不知道,可能是Fruit、Apple或者其他子类, 编译器在p赋值以后,盘子里面没有标记为“Apple",只是标记了一个占位符“CAP#1”(可以通过javap反编译字节码来严重),来表示捕获一个Fruit或者Fruit的子类。
但是不管是不是通配符的写法,泛型终究指的是一种具体的类型,而且被编译器使用了特殊的“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子类的类。
但是使用的时候必须使用object来引用。
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是producer extends consumer super的缩写。
也是对我们上面的分析的一个总结
意思是extends用于生产者模式,而super用于消费者模式。
消费者模式:比如上面的callback结果是为了消费;这些结果被消费处理。
生产者模式:比如那些Converter,我们要处理特定格式的http请求,需要生产不同的转换器Converter。
推荐学习:《java视频教程》
以上是一起来分析Java泛型和泛型的通配符的详细内容。更多信息请关注PHP中文网其他相关文章!