曾几何时,一直痛恨java的泛型擦除,为了适配老版的jdk,java引入泛型的同时,引入了泛型擦除机制,导致想要获取类中的泛型,需要都个圈子,具体可以看我这篇博客,获取泛型的类。
前不久使用Mybatis-plus分页,但发现PO对象不满足接口出参数据,因此需要拼接数据转换成VO对象,但分页信息不变,刚想写个方法适配的,结果发现Mybatis-plus已经考虑到这点了,有个convert方法。具体如下:
/**
* IPage 的泛型转换
*
* @param mapper 转换函数
* @param <R> 转换后的泛型
* @return 转换泛型后的 IPage
*/
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
List<R> collect = this.getRecords().stream().map(mapper).collect(toList());
//此处强转,是为了通过setRecords方法对于List<T>的校验。为何不会报错:因为在运行期间IPage<R> 和Ipage<T>是一样的,里面存的类型其实是Object,强转后编译器就认为此时Records里面的类型是R,因此不会报错,运行时也不会报错。
return ((IPage<R>) this).setRecords(collect);
}
/**
* 设置分页记录列表
*/
IPage<T> setRecords(List<T> records);
上述方法其实也有多种实现,如下
第一种(不强转)
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
//此处不说明List集合里面的对象类型,List<T>和List<R>都是List对象,因此转成List可以绕过编译器检测,因为泛型擦除机制,运行时不会校验类型。
List collect = this.getRecords().stream().map(mapper).collect(toList());
return this.setRecords(collect);
}
第二种(强转)
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
//此处也是通过强转,绕过编译器检测,使其在编译期无法校验泛型的类型。
List<R> collect = this.getRecords().stream().map(mapper).collect(toList());
return ((IPage) this).setRecords(collect);
}
如果你搞懂了上面的例子,你也就能明白下面的例子了
List<String> s = null;
List<Integer> i = null;
//报错
s = (List<Integer>)i;
List tmp = i;
//不报错
s = (List<String>) tmp;
//不报错
s = tmp;
最后解释下上面的例子,s和i一开始就分别定义成List和List,
因此他们的数据类型是不一致的,所以无法赋值,强转也不行(因为已经显示说明了他们的数据类型了,此处得区别于(IPage®this的情况))。
而经过List tmp = i , 已经将数据类型擦除,绕开了编译器检测,因此 s = tmp
不会报错(可以认为是一种编译器优化,不需要显示强转,由程序员保证类型一致), s = (List<String>) tmp 也不会,直至显示调用get方法转换成String 对象时才会报Integer对象类型无法转换成String类型。