21

Java ArrayList<T>(可能还有许多其他类)内部发生的事情是有一个 internal Object[] array = new Object[n];T对象被写入其中。每当从中读取一个元素时,return (T) array[i];就会进行一次强制转换。所以,每读一次。

我想知道为什么这样做。对我来说,他们似乎只是在做不必要的演员表。T[] array = (T[]) new Object[n];只创建一个然后不进行强制转换不是更合乎逻辑并且稍微快一点return array[i];吗?这只是每个数组创建的一次强制转换,通常少于读取次数。

为什么首选他们的方法?我不明白为什么我的想法严格来说不是更好?

4

4 回答 4

18

它比这更复杂:泛型在字节码中被擦除,而T[]is的擦除Object[]。同样,返回值get()变为Object。为了保持类型系统的完整性,在实际使用类时插入检查强制转换,即

Integer i = list.get(0);

将被抹去

Integer i = (Integer) list.get(0);

在这种情况下,ArrayList 中的任何类型检查都是多余的。但这真的是无关紧要的,因为(T)(T[])都是未经检查的强制转换,并且不会产生运行时开销。

可以编写一个经过检查的 ArrayList 来执行以下操作:

T[] array = Array.newInstance(tClass, n);

这将防止堆污染,但代价是冗余类型检查(您不能在调用代码中抑制合成转换)。它还需要调用者向 ArrayList 提供元素类型的类对象,这会使其 api 变得混乱,并使其更难在通用代码中使用。

编辑:为什么禁止创建通用数组?

一个问题是检查了数组,而未检查泛型。那是:

Object[] array = new String[1];
array[0] = 1; // throws ArrayStoreException

ArrayList list = new ArrayList<String>();
list.add(1); // causes heap pollution

因此,数组的组件类型很重要。我认为这就是为什么 Java 语言的设计者要求我们明确使用哪种组件类型。

于 2012-09-11T09:00:28.537 回答
4

每当从中读取一个元素时,return (T) array[i];就会进行一次强制转换。所以,每读一次。

Generic 是编译时检查。在运行时使用类型 T extends 代替。在这种情况下是T隐式extends Object的,所以你在运行时拥有的是有效的。

return (Object) array[i];

或者

return array[i];

仅仅创建一个不是更合乎逻辑并且稍微快一点吗?

T[] array = (T[]) new Object[n]

并不真地。再次在运行时这变成

Object[] array = (Object[]) new Object[n];

或者

Object[] array = new Object[n];

你真正要钓鱼的是

T[] array = new T[n];

除了这不能编译,主要是因为 T 在运行时是未知的。

你能做的是

private final Class<T> tClass; // must be passed in the constructor

T[] array = (T[]) Array.newInstance(tClass, n);

只有这样,数组才会真正成为预期的类型。这可以使读取更快,但以写入为代价。主要的好处是快速检查失败,即您将停止损坏的集合,而不是等到您检测到它已损坏才抛出异常。

于 2012-09-11T09:04:42.360 回答
2

我认为更多的是代码风格问题,而不是性能或类型安全(因为支持数组是私有的)

java 5ArrayList是按照您建议的E[]数组方式实现的。如果您查看源代码,您会发现它包含 7 个 (E[]) 类型转换。从 java 6ArrayList更改为使用Object[]仅导致 3 (E) 强制转换的数组。

于 2012-09-11T09:22:07.580 回答
-1

数组也是对象。在这里T[] array = (T[]) new Object[n],您只转换 (T[]) 对象类型而不是数组中的元素。

于 2012-09-11T08:55:09.917 回答