说明:
今天,继续学姐java编程思想-泛型这一章的时候,对于通配符的理解不够明白,于是又网上查了些资料,发现一篇写的很好的文章,在此记录学习一下.
原作者传送门
http://build.cthuwork.com:8081/wordpress/category/java%E6%95%99%E7%A8%8B/java%E5%86%8D%E8%B0%88%E6%B3%9B%E5%9E%8B/
首先本文假定读者对Java的泛型有基础的了解,若需要请参考其他资料配合阅读。
“当java有了泛态和多态重写两个特性。两件快乐的事情重合在一起。而这两份快乐,又能带来更多的自由。得到的,本该是像梦境一般的自由设计模式……但是,为什么呢,为什么会变成这样呢……” -白·雨轩
泛型作为Java1.5添加到Java语言中的一个重要特性,起着提高类型安全、增加程序可读性等等作用。但是泛型在Java中的实现方式和某些行为却饱受诟病,最常见的说辞莫过于Java的泛型是伪泛型,会被擦除了。但其实之所以会拿这个说法来给泛型差评,其实很大程度上只是因为这是最容易理解的问题罢了,所以常常被一些一知半解的人拿来说事。Java泛型不尽人意的地方远不止这一个问题,但其他的不尽人意的地方大多都因为其本身的复杂性被大多数人忽略了。因为大多数人宁愿只使用简单的泛型特性也不愿去研究如何做到最大化的类型安全。甚至只会使用Java的API中的泛型类,根本不清楚、没尝试如何去正确实现一个自己的泛型类。
所以今天我们来聊聊其中一个不尽人意的地方:泛型函数重写。
重写作为多态的一个重要特性。因为Java中所有对象方法在被JIT优化前都会被当成虚函数,所以重写起着至关重要的用途。使用多态的动态单分派和虚函数表来实现虚函数的特性。重写本身在Java中的实现是十分优雅的,要是没记错的话应该是在Java1.5的时候还增加了返回值类型协变的特性,使得在使用特化的泛型类子类的时候不需要强转返回值了。
但是,当作为多态的招牌:“重写”,遇上作为类型安全的招牌:“泛型”的时候有些事情就不那么快乐了。本来两件快乐的事情重合在一起,会得到更多的快乐……但是,为什么呢,为什么会变成这样呢……(白学家打死www)。
其实问题归根结底还是因为泛型和多态绕在一起后形成的复杂度。我们先来考虑一下,什么样的重写才是正确且不会出现类型问题的:
1.函数标识符应该完全。
1.函数参数应该是完全相同的,不能允许任何的协变或逆变。
2.函数返回值应该兼容。这意味着返回值可以允许协变。
当符合以上3条时,我们就可以说重写是正确的。那么,如果我们说泛型和重载 在!一!起! 在!一!起!(某MV中毒www)的时候会有啥问题,那么也只能出现在返回值上。好,那我们就把重点放在返回值上!
首先我们先要明白,什么叫协变。在没有泛型之前,协变指的是从为子类隐式转换父类,这很好理解。在有了泛型后,就有点不同了。首先泛型本身是不支持协变的,除非你使用通配符(从数组吸取的教训)。当你使用通配符后,泛型就是可以协变的了,这时候就由要转换的类型是否和通配符兼容来决定。
那么也就是说,如果我们尝试重写一个返回值包含有TypeVariable的函数,那么要么返回值一模一样、要么协变到拥有同样父类泛型参数的子类、要么把其中的泛型参数协变为其通配符兼容的泛型参数、要么类型本身和泛型参数一起协变。
现在我们来看看这段代码:
interface I1 {
}
interface I2 extends I1 {
}
interface I3 extends I2 {
}
class A {
List foo() {
return null;
}
}
class B extends A {
@Override
List foo() {
return null;
}
}
这段代码的意图在于想让B的foo函数返回一个List
List extends I1> foo() {
return null;
}
}
class B extends A {
@Override
List extends I2> foo() {
return null;
}
}
现在呢?这段代码是可以通过编译的。那问题到底出在哪里?我们再尝试将代码改为
class A {
extends I1> List extends T> foo() {
return null;
}
}
class B extends A {
@Override
extends I2> List extends T> foo() {
return null;
}
}
这种状态下通配符的范围显而易见至少应该和第2组代码一样。但是这段代码仍然不能通过编译。这时候大多数人基本上都弃治了,要么认为遇到了BUG,要么各种病急乱投医。那么问题到底在哪?其实问题的根本在于如果想重载泛型函数的话那么要么不写任何TypeVariables,要么函数的TypeVariables必须一模一样。What!?Excuse me???不是说重载是否正确只取决于那3条么?怎么还要要求TypeVariables一模一样?
首先要声明的一点是所谓的TypeVariables必须一模一样是指TypeVariables的Bounds必须相同,所以说标识符是可以不同的,比如 T a()和 A a()的重载是合法的。其实之所以要设计成要求TypeVariables一模一样是有原因的。我们考虑下面的代码:
// Abstract Stage
interface Shitagi {
}
interface Pants extends Shitagi {
}
interface BlueWhitePants extends Pants {
}
interface PinkPants extends Pants {
}
interface BearPants extends Pants {
}
// Real Stage
interface RealShitagi extends Shitagi {
}
interface RealPants extends Pants, RealShitagi {
}
interface RealBlueWhitePants extends BlueWhitePants, RealPants {
}
interface RealPinkPants extends PinkPants, Pants {
}
interface RealBearPants extends BearPants, RealPants {
}
class A<E extends Shitagi> {
A foo() {
return null;
}
}
class B<E extends RealShitagi> extends A<E> {
@Override
B foo() {
return null;
}
}
在看下面这一段前,先S!T!O!P。想想到底这玩意到底有没有问题。然后如果有问题,问题在哪?
=======================EMPTY AREA=======================
首先这段代码仍然不能被编译(一定是因为太污,所以编译器拒绝与您说话并丢出了辣鸡BUG www),但是这段代码更能体现TypeVariables不相同所导致的问题。首先当我们调用B.foo的时候返回的类型的通配符范围是B
class A<E extends Shitagi> {
extends Pants> A foo() {
return null;
}
}
class B<E extends RealShitagi> extends A<E> {
@Override
extends Pants> B foo() {
return null;
}
}
这样编译器就能直接检测出B.foo的返回值类型B<>(这里的2个括号并不是打多了,是表示返回值的类型并不是一个Wildcard,而是由调用函数时提供的泛型参数T来决定的)和B本身的泛型参数B不兼容。就能避免出现类型安全问题。比如如果这里的T是PinkPants的话,那么返回值类型就必须为B,和B不兼容。
好那么问题来了,我们怎么修复我们的代码,使其可以工作?嗯,这个问题才是关键。首先我们来看看刚刚那个例子,刚刚那例子的问题在于B.foo的返回值,要使B.foo的既和A.foo兼容,又和B的泛型参数兼容,就只能这么做:
class A<E extends Shitagi> {
extends Pants> A extends T> foo() {
return null;
}
}
class B<E extends RealShitagi> extends A<E> {
@Override
extends Pants> B extends T> foo() {
return null;
}
}
这样改动后B.foo的返回值类型就是B
class A<E extends Shitagi> {
extends Pants> A extends T> foo() {
return null;
}
}
class B<E extends RealShitagi> extends A<E> {
@Override
extends Pants> B extends RealPants> foo() {
return null;
}
}
这段代码行不行?当然不行,首先第一个问题是返回值类型和A.foo不兼容,其次问题是T被吃掉了。到这里大部分人都一脸懵逼了,没办法动T,怎么玩?一些会玩黑魔法的码农也许会创建一个class C extends B。但是这是异端,设计上毫无美感,并且以后扩展程序时也可能会遇到问题。既然我们没办法动TypeVariables,那么能不能把T的上界设置为某个类的TypeVariable,然后对其动手?好想法,我们来实际尝试一下:
class A<E extends Shitagi, BASE extends Pants> {
extends BASE> A extends T, BASE> foo() {
return null;
}
}
class B<E extends RealShitagi, BASE extends RealPants> extends A<E, BASE> {
@Override
extends BASE> B extends T, BASE> foo() {
return null;
}
}
我们给类加了一个名为BASE的泛型参数,用于传递T的返回值类型。这段代码能否通过编译?当然可以,这段代码没有任何问题。唯一缺点就是在创建B的对象时需要手工指定一个多余的泛型参数,当然我们也可以直接写成:
class B<E extends RealShitagi> extends A<E, RealPants> {
@Override
extends RealPants> B extends T> foo() {
return null;
}
}
如果我们不需要B还有子类,这是目前看来的最佳解决方案。等会,这样B.foo的TypeVariable是不是和A.foo的不一样了?其实还是一样的,毕竟在B里面来看A,BASE这个泛型参数就是RealPants,毕竟在继承的时候就已经被特化为RealPants了。所以没有任何问题,只是写法有些许区别。好,那么如何解决返回值类型的泛型参数是Wildcard的问题?那就更简单了,直接删掉Wildcard解决问题。代码能通过编译,完事OK?目前看来确实是只要把PAD塞进去就完事OK了(wwwww)。毕竟这种问题还是可以通过一(塞)些(入)tr(P)ic(A)k(D)来解决的。
那么现在问题来了,有没有什么情况是用这种最终手段解决不了的。答案是有。假设我们现在Shitagi系列的本身接口本身就自带泛型,我们来看下面的代码:
// Base Ningen
interface Ningen {
}
interface Onnanoko extends Ningen {
}
interface Otoko extends Ningen {
}
// Real Ningen
interface KanameMadoka extends Onnanoko {
}
interface EnkanNoKotowari extends KanameMadoka {
// 圆环之理指引着你www
}
interface ShiinaMashiro extends Onnanoko {
}
interface ShiinaMakuro extends ShiinaMashiro {
// 椎名真黑:实行Plane Cwww
}
interface OgisoSetsuna extends Onnanoko {
}
interface Sprite extends OgisoSetsuna {
// 老板来一箱82年的雪碧!
}
interface NoumiKudryavka extends Onnanoko {
}
interface NoumiKudryavkaAnatolyevnaStrugatskaya extends NoumiKudryavka {
// Wafu,你可识得库特全名www
}
interface ItouMakoto extends Otoko {
}
interface KinoshitaHideyoshi extends Ningen {
// 秀吉的性别就是秀吉www
}
// Abstract Stage
interface Shitagi<N extends Ningen> {
}
interface Pants<N extends Ningen> extends Shitagi<N> {
}
interface BlueWhitePants<N extends Ningen> extends Pants<N> {
}
interface PinkPants<N extends Ningen> extends Pants<N> {
}
interface BearPants<N extends Ningen> extends Pants<N> {
}
// Real Stage
interface RealShitagi<N extends Ningen> extends Shitagi<N> {
}
interface RealPants<N extends Ningen> extends Pants<N>, RealShitagi<N> {
}
interface RealBlueWhitePants<N extends Ningen> extends BlueWhitePants<N>, RealPants<N> {
}
interface RealPinkPants<N extends Ningen> extends PinkPants<N>, Pants<N> {
}
interface RealBearPants<N extends Ningen> extends BearPants<N>, RealPants<N> {
}
class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen> {
, NEW_N extends Ningen> A gift(NEW_N ningen) {
return null;
}
}
class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen> extends A<E, ORIGIN_N> {
@Override
, NEW_N extends Ningen> B gift(NEW_N ningen) {
return null;
}
}
现在我们把B.foo改成了B.gift用于gift胖次www。首先由于TypeVariables不同,所以是这段代码无法通过编译。那么能不能像刚刚那样解决?如果你尝试使用上面的方法解决问题,你会发现行不通了,因为RealPants现在带了一个泛型参数NEW_N。这就尴尬了,而NEW_N这个泛型参数是在调用函数的时候才能使用的,在类里面是找不到的。
这里有一种很不美观且晦涩难懂的解决办法,将NEW_N放到类里面:
static class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> {
A>, ?> gift(NEW_N ningen) {
return null;
}
@SuppressWarnings("unchecked")
static , ORIGIN_N extends Ningen, NEW_N extends Ningen> A, NEW_N> prepare(A?> a) {
return (A, NEW_N>) a;
}
}
static class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen>
extends A<E, ORIGIN_N, BASE, NEW_N> {
@Override
B>, ?> gift(NEW_N ningen) {
return null;
}
@SuppressWarnings("unchecked")
static , ORIGIN_N extends Ningen, NEW_N extends Ningen> B, NEW_N> prepare(B?> a) {
return (B, NEW_N>) a;
}
}
这样代码就能通过编译了,使用时我们用一种很别扭的方式:
B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants>, ?> b = <Other Code>;
ShiinaMashiro mashiro = <Other Code>;
B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants>, ?> new_b = B.<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, ShiinaMashiro>prepare(b).<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro);
先强转,再调用(嗯,非常Mashiro,Mashiro得我都想给设计Java泛型的人执行Plan C寄刀片了www)。以下是上面代码使用实例函数的实现:
class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> {
A>, ?> gift(NEW_N ningen) {
return null;
}
@SuppressWarnings("unchecked")
final A, P_NEW_N> prepareA() {
return (A, P_NEW_N>) this;
}
}
class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen>
extends A<E, ORIGIN_N, BASE, NEW_N> {
@Override
B>, ?> gift(NEW_N ningen) {
return null;
}
@SuppressWarnings("unchecked")
final B, P_NEW_N> prepareB() {
return (B, P_NEW_N>) this;
}
}
调用改为:
B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants>, ?> b = <Other Code>;
ShiinaMashiro mashiro = <Other Code>;
B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants>, ?> new_b = b.<ShiinaMashiro>prepareB().<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro);
首先本文假定读者对Java的泛型有基础的了解,若需要请参考其他资料配合阅读。
泛型的泛参(type argument)可以使用实际类型或者通配符(wildcard)。其中通配符可以通过边界(bound)来限制其接受的实际参数的类型。根据其种类,可以分为无界(unbounded)、上界(upper bound)和下界(lower bound)。其泛型边界决定了输入(input)和输出(output)分别能接受什么类型。
输入为其函数的参数、属性能够赋值的值的类型,输出为函数的返回值、获取到的属性的值的类型。
以下将详细描述。
一、实际类型
泛型的泛参可以使用实际类型。也就是类似于List,直接指定泛型的类型。这时候泛型的表现最容易理解,输入和输出都为实际类型。需要注意的一点是,泛型不支持协变(Covariant),协变需使用通配符。为什么泛型不支持协变呢。我们先从支持协变的数组开始考虑。考虑以下代码:
Object[] array = new String[1];
array[0] = 12.450F;
这段代码是可以通过编译的,然而会让静态类型的Java语言在没有任何强制类型转换的情况下出现类型异常。我们尝试往一个String类型的数组索引为0的位置赋值一个Float类型的值,这当然是行不通和完全错误的。Java数组能够协变是一个设计上的根本错误,它能导致你的代码在你完全不知情的情况下崩溃和异常,但现在改已经为时已晚。幸好我们有和经常使用集合API,否则最常见的情况可能如下:
public Number evil;
public void setAll(Number[] array) {
for (int i = 0;i < array.length;i++) {
array[i] = evil;
}
}
public void looksGood() {
atSomewhereWeDontKnown(); //We summoned evil to our kawaii and poor code
Float[] ourKawaiiArray = getOurKawaiiArray(); //Oops
}
public void atSomewhereWeDontKnown() {
evil = 12450;
}
public Float[] getOurKawaiiArray() {
Float[] weWantFloatFilled = new Float[0xFF];
setAll(weWantFloatFilled); //Buts... we got (1)E(2)V(4)I(5)L(0)...
return weWantFloatFilled;
}
我们可不想让(1)E(2)V(4)I(5)L(0)充满我们的代码。所以,泛型吸取了这个教训,本身就是为了提高类型安全性而设计的泛型不能犯这样的低级错误。所以你不能写以下代码:
List<Object> array = new ArrayList<String>;
array.set(0, 12.450F);
这段代码在第一行就无法通过编译,因为你尝试协变一个泛型。其解决办法和其他的说明将在后续讨论。
二、通配符
1.无界通配符
无界通配符为”?”,可以接受任何的实际类型作为泛参。其能接受的输入和输出类型十分有限。
①可用输入类型
严格意义上不能接受任何的类型作为输入,考虑以下代码:
List> list = new ArrayList();
list.add("123");
你可能觉得这段代码看起来没有问题。通常会这样考虑,我们可以简单的把无界通配符”?”看成Object,往一个Object类型的列表加一个String有什么问题?况且其实际就是String类型。其实并不能通过编译,这并不是编译器出现了错误。这里有个逻辑漏洞,我们仔细考虑无界通配符的意义。无界通配符代表其接受任何的实际类型,但这并不意味着任何的实际类型都可以作为其输入和输出。其语义上有微妙的但巨大的区别。其含义是不确定到底是哪个实际类型。可能是String,可能是UUID,可能是任何可能的类型。如果这是个UUID列表,那么往里面加String等就会出事。如果是String列表,往里面加UUID等也会出事。或者我们不管其是什么类型的列表,往里面加Object,然而Object里有你的实际类型的属性和方法么。即使实际是Object列表,我们也无法确定。那么,无界通配符就不能接受任何输入了么,看起来是这样。其实有个例外,null作为一个十分特殊的值,表示不引用任何对象。我们可以说String类型的值可以为null、UUID类型的值可以为null,甚至Object类型的值可以为null。无论是什么类型,都可以接受null作为其值,表示不引用任何对象。所以无界通配符的输入唯一可接受的是可为所有类型的null。
②可用输出类型
无界通配符的输出类型始终为Object,因为其意义为接受任何的实际类型作为泛参,而任何的实际类型都可以被协变为Object类型,所以其输出类型自然就为Object了。没有什么需要注意的地方。
2.上界通配符
上界通配符为”extends”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。这种用法非常少用,我在很多开源项目中基本没看到这种用法。由于这和本章内容无关,不影响输入和输出的类型,所以暂不描述。
①可用输入类型
严格意义上同样不能接受任何的类型作为输入,出于严谨目的,我们再从头分析一遍,这次以Minecraft的源代码为例,考虑以下代码:
List extends EntityLiving> list = new ArrayList();
list.add(player);
你可能觉得这段代码又没问题了,EntityPlayer确实继承了EntityLiving。往一个EntityLiving的列表里加EntityPlayer有什么问题?放肆!12450!好不闹/w\。这里的问题在于如果实际上是EntityPig的列表呢。这么想你就应该懂了,和无界通配符差不多,其只是限定了列表必须是EntityLiving的子类而已,我们并不知道实际是什么。所以在这里我们只能添加EntityLiving类型的对象。是不是觉得有什么不对?对了,我就是超威蓝猫!好不闹/w\,我们能在EntityLiving上调用EntityPlayer的getGameProfile么,明显不能,况且我们到底能不能实例化EntityLiving也是个问题。这里真的很容易混淆概念,一定要牢记,只能使用null作为上界通配符的输入值。
②可用输出类型
好了,这次终于能玩了,上界通配符的输出类型为其指定的类型,实际上如果通配符位于泛型类的声明中例如:
public class Foo<T extends EntityLiving> {
public T entity;
}
这个类中entity字段的实际类型不是所有类型的父类Object了,而是EntityLiving,这可以用查看字节码的方式证实。当然其类型是Object也不会有太大的差别,可以想到的问题是当我们以某种方式往其内部传入了Object类型或其他不是EntityLiving类型或其子类的对象时,可能会出现类型转换异常或者更严重的留下随时代码会崩溃的隐患。而直接使用EntityLiving类型作为其实际类型就会在尝试这么做的同时抛出类型转换异常,从而避免这种问题。
3.下界通配符
下界通配符为”super”,可以接受其指定类型或其父类作为泛参。可能很多人都没有用过下界通配符,因为其真的很少用。其主要用处之一是在使用Java或第三方的API的泛型类时,对泛参类型不同,但泛参具有继承关系,且主要关注其输入的泛型对象进行归纳。以Minecraft的源码为例,考虑以下代码:
private EntityMob ourKawaiiMob;
private EntityMob otherKawaiiMob;
public int compareMobEntity(Comparator super EntityMob> comparator) {
return comparator.compare(ourKawaiiMob, otherKawaiiMob);
}
此方法可以接受一个比较器,用于比较两EntityMob。这里的含义是,我们希望接受一个EntityMob或其父类的比较器。例如Comparator只会把EntityMob看成一个Entity进行比较,这样我们就可以对EntityMob的某一部分进行比较。我们不能将一个完全不是EntityMob的父类的比较器,例如Comparator作为参数传入。也不能将一个EntityMob的子类的比较器,例如Comparator作为参数传入。因为实际我们比较的是EntityMob或其子类的对象,即使我们传入的是其子类的比较器,我们也不能保证不会发生用Comparator比较一个EntityEnderman的情况。又或者即使我们利用Java的类型擦除这么做了,java的动态类型检查会强制抛出ClassCastException。所以在这种情况下应该使用下界通配符。
①可用输入类型
下界通配符的输入类型为其指定的类型或子类。因为其意义为接受其指定类型或其父类作为泛参。那么无论我们提供的对象是什么类型,只要是其指定的类型或子类的对象,那么毫无例外一定是其指定的类型的对象。我们不能提供其指定的类型的父类作为对象,考虑以下代码:
private EntityLiving our;
private EntityLiving other;
Comparator super EntityMob> comparator = new EntityMobComparator();
comparator.compare(our, other);
这段代码不能通过编译,我们尝试用一个EntityMob的比较器来比较EntityLiving。不仔细考虑可能以为这并没有什么问题,EntityMob的比较器完全有能力来比较EntityLiving啊?但是实际情况是如果这段代码成功编译,而且没有动态类型检查的话EntityMob的比较器就可能会尝试其获取EntityLiving并没有的,属于EntityMob的属性,然后就会获取到非法的数据,或导致Java运行时崩溃,这当然是不行的。好在我们即使这么做了,Java也会强制抛出ClassCastException。
②可用输出类型
下界通配符的输出类型始终为Object,因为其意义为接受其指定类型或其父类作为泛参,我们并不知道具体是哪一个父类。而任何的实际类型都可以被协变为Object类型,所以其输出类型自然就为Object了。
三、回顾泛型边界和输入输出类型的区别
泛型边界并不直接代表着能接受的输入输出的类型,其含义为能接受什么样的实际类型。而输入输出类型能是什么则是根据泛型边界的含义得出的,其中的限制是由于我们只能通过泛型边界对实际类型进行猜测而产生的,希望大家能仔细理解其中的含义。
首先本文假定读者对Java的泛型有基础的了解,若需要请参考其他资料配合阅读。
泛型系统是作为Java 5的一套增强类型安全及减少显式类型转换的系统出现的。泛型也叫参数化类型,顾名思义,通过给类型赋予一定的泛型参数,来达到提高代码复用度和减少复杂性的目的。
在Java中,泛型是作为语法糖出现的。在虚拟机层面,并不存在泛型这种类型,也不会对泛型进行膨胀,生成出类似于List、List之类的类型。在虚拟机看来,List这个泛型类型只是普通的类型List而已,这种行为叫泛型擦除(Type Erasure)。
那么在Java中泛型是如何如何实现其目的的呢?Java的泛型充分利用了多态性。将无界(unbounded)的通配符(wildcard)理解为Object类型,因为Object类型是所有除标量(Scalar)以外,包括普通的数组和标量数组的类型的父类。将所有有上界(upper bound)的通配符理解为其上界类型例如将被理解为CharSequence类型。并在相应的地方自动生成checkcast字节码进行类型检查和转换,这样就既可以实现泛型,又不需要在字节码层面的进行改动来支持泛型。这样的泛型叫做伪泛型。
下面是几个帮助理解的例子
public class Foo {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return this.value;
}
public static void main(String[] args) {
Foo foo = new Foo();
foo.set("foo");
String value = foo.get();
}
}
编译后:
public class Foo {
private CharSequence value;
public void set(CharSequence value) {
this.value = value;
}
public CharSequence get() {
return this.value;
}
public static void main(String[] args) {
Foo foo = new Foo();
foo.set("foo");
String value = (String) foo.get();
}
}