【Android】为什么说Java的泛型是“假泛型”?


Java泛型

Java的泛型是JDK5带来的新特性,它有如下的优点:

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定
  • 泛型归根到底就是“模版”

但是,为了做到向下兼容,Java中的泛型仅仅是一个语法糖,并不是C++那样的真泛型。

如何证明呢?我们可以看看下面的例子

证明

在这个例子中,我们定义了一个List<Integer>集合,我们可以调用add方法向其中加入Integer类型的值。

如果我们像下面一样调用add方法向里面加入String,当然会报错:

错误信息如下:

add (java.lang.Integer) in List cannot be applied to (java.lang.String)

显然,我们是没法直接向其中加入String类型的值的。

不过,我们可以另辟蹊径。我们尝试通过反射,间接地调用add方法,向这个List<Integer>中加入String类型的值。

运行发现,并没有报错,而且我们成功地打印了这个str!

[1, str]
str

由此可见,所谓的泛型确实是假泛型。原本只能装入Integer的List中,我们成功装入了一个String类型的值。

类型擦除

实际上,Java 的泛型仅仅在编译期有效,在运行期则会被擦除,也就是说所有的泛型参数类型在编译后都会被清除掉。这就是所谓的类型擦除

我们看到下面的例子:

我们在这个类中定义了两个方法,传入List<String>List<Integer>这两个不同类型的参数,看起来并没有什么问题。

可是,编译器却报出如下的错误:

Method addList(List) has the same erasure addList(List) as another method in type Test

也就是说,这两个方法的签名在进行了类型擦除后均为addList(List<E>)

下面是关于List的类型擦除的一些情况:

  • List<String>List<T>擦除后的类型为List
  • List<String>[]List<T>[] 擦除后的类型为 List[]
  • List<? extends E>List<? super E>擦除后的类型为List<E>
  • List<T extends Serialzable & Cloneable>擦除后类型为 List<Serializable>

C++模板

C++的模板就是我们所谓的"真泛型",下面是一段C++使用模板的类的示例:

在类Test中存储了类型为T的对象。很有趣的一点是我们在Test::try()方法中调用了objfun()方法。它怎么知道fun()方法是T所包含的呢?

在我们调用Test的构造函数实例化这个模板时,编译器进行了检查,发现A类确实含有fun方法,因此编译可以通过。

如果A类并不含有fun方法,则编译不会通过。这样类型安全就得到了保障。

在 C++ 模板中,编译器使用提供的类型参数来扩充模板,因此为List<A>生成的 C++ 代码不同于为 List<B>生成的代码,List<A>List<B> 实际上是两个不同的类。

总结

虽然java中的泛型是“假”的,会有类型擦除的操作。但是不可否认,泛型的引入对Java语言影响仍然是非常大的。

因此在我们使用泛型的时候,应当尽量考虑到类型擦除这个特点,多考虑一下自己的封装是否类型安全。


Android Developer in GDUT