浅谈Java泛型中的类型擦除以及编程中的典型错误

来源:互联网 发布:订机票 知乎 编辑:程序博客网 时间:2024/05/16 11:04

Java语言内置了泛型机制,使得我们可以使用一种集合对象便可以存储任意类型的对象,但是泛型机制也并不是能够随心所欲的使用的,很多时候我们不正确的使用可能会使我们产生疑惑。比如现在我们想要实现一个类似于ArrayList的数据结构,假设命名为MyArrayList,显而易见的,我们需要在MyArrayList中定义一个数组来存储元素,考虑到MyArrayList应该能够存储任意对象,我们应该把它设置为一个泛型类,如下的代码是显而易见的:

public class MyArrayList<T> {}

那么,数组该怎么定义呢?是使用泛型数组,还是Object类型的数组?

private T[] datas;private Object[] datas;

如果稍不注意,我们可能就会写成泛型数组的形式,但是很可惜,使用泛型数组的做法是错误的,查阅ArrayList源码我们可以发现,Java内部实现如下:

/**     * The array buffer into which the elements of the ArrayList are stored.     * The capacity of the ArrayList is the length of this array buffer. Any     * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to     * DEFAULT_CAPACITY when the first element is added.     */    private transient Object[] elementData;

我们可以看到ArrayList使用Object[]数组完成数据的存储,如果使用泛型数组,那么在构造器中我们如果写下这样的代码:

datas = new T[10];

IDE会报错,报错信息为:Cannot create a generic array of T

之所以会出现这样的错误,是因为Java的泛型机制中有类型擦除的过程,而这一过程,就是上述报错信息的根源,现在我们来探讨一下类型擦除。在讨论类型擦除之前,我们有必要回顾一下数组。

在Java中,我们可以创建两种类型的数组:基本类型数组,以及自定义类型数组。这两种数组在编译期就会受到严格的类型检查,考虑以下代码:

int[] a = new int[2];a[0] = 1;a[1] = false;

显然这段代码是无法通过编译的,基本类型数组表现出的行为自定义类型数组同样适用,假设现在有一个自定义Shape类,并且创建一个Shape类型的数组,那么在编译时会检查该数组内的元素是否都是Shape类型的。正是因为这样的原因,下面的代码是错误的:

T[] a = new T[]; //如果T在任何地方都没有被定义

因为这样的写法编译器无法在编译时检查a数组内的元素是否属于T类型。现在回到泛型,Java的泛型是通过类型擦除机制实现的,简而言之,如果定义一个ArrayList<Shape>和一个ArrayList<Car>,Java都把它们视为Object,除了Object类的信息全部被擦除了。考虑下面一段代码:

public class A<T> {private T t;public A(T t) {this.t = t;}public void doSomething() {t.f();}}

上述代码中定义了一个泛型类A,然后定义一个类B,B中拥有方法f();

public class B {public void f() {//dosomething....}}

期望的结果是A持有一个B的对象,然后调用A的doSomething()方法,期待其自动调用B的f()方法,想法很美好,但是实际上这段代码是不能通过的,原因就是因为类型擦除,A虽然是泛型类,但是被擦除成了Object,而Object类中没有f()方法,编译器也无法确定运行时传入的类型对象是否含有f()方法(需要注意的是,如果是C++,上述代码可以是正确的)。

通过以上的分析,我们大致了解了Java泛型中的类型擦除以及带来的潜在编程问题,希望大家看完我的博客能够对泛型和数组有更深刻的了解。这些内容在《Java编程思想》中均有所涉及并且十分详尽,还有疑问可以参考。


0 0
原创粉丝点击