【问题标题】:ArrayList's instantiating of the inner T[] arrayArrayList 对内部 T[] 数组的实例化
【发布时间】:2017-04-02 19:48:22
【问题描述】:

我发现如果你有一个泛型类,其数组依赖于该类的类型参数,你不能以通常的方式初始化该数组:

class Foo<T>  {
    private T[] a;

    Foo()  {
       a=new T[5]; //doesn't compile
    }
}

你只能这样做:

class Foo<T>  {
        private T[] a;

        Foo(T[] a)  {
           this.a=a;
        }
    }

但是这个代码是可能的:

ArrayList<Integer> list=new ArrayList<>();

为什么? ArrayList 是如何克服这个问题的?我查看了它的代码,但无法弄清楚。它似乎只是将东西存储在 Object[] 数组中,这似乎是错误的。

【问题讨论】:

  • 如果错了,ArrayList 会起作用。但它有效。所以没有错。为什么你认为这是错误的?
  • @JB Nizet,因为即使没有泛型我们也可以做到这一点,在内部使用 Object[] 有什么意义。泛型的用处不在于可以精确存储类型T吗?
  • 由于T 在编译时被擦除,因此代码new T[size] 需要编译为new Object[size],这让我们拥有能够保存any 对象的数组。但在第二种情况下,编译器能够检测我们是否传递数组已经存在并且它的类型匹配指定的T类型。
  • @parsecer 泛型在运行时被删除,这使得 new T[] 不可能。但是您不应该关心内部数组的类型,因为它是私有的,因此不是公共 API 的一部分。重要的是列表是类型安全的,并且编译器会阻止将字符串添加到 List。列表内部存储 Integer 的位置和方式无关紧要。

标签: java arrays arraylist


【解决方案1】:

ArrayList 中的数组始终是Object[] 类型,因为您可以在这样的数组中存储任何内容。 type 参数在方法级别上实现了类型安全,这很重要。

根据要求编辑以解决类型擦除的主题:

Java 中的类型擦除意味着泛型类型参数仅在编译时出现,此时确保类型安全最为重要。泛型已以这种方式实现,以保持 Java 的新版本与旧版本兼容。

这样做的缺点是永远无法实例化泛型类型,因为它在运行时永远不知道。

【讨论】:

  • 覆盖类型擦除对于很好地回答这个问题至关重要。
  • 那么为什么在所有示例中它们都将T a 之类的单独变量存储在T 类型中,而不是Object
  • @parsecer:因为 T[] 与 T 不同。Java 泛型并不总是能很好地处理数组。另请参阅How to create a generic array in Java?
【解决方案2】:

将其视为类比:

您拥有一家铅笔公司。你在面包车里带着铅笔四处走动。

你可以在你的面包车里放花:毕竟这是一辆面包车,所以你可以在里面放任何你想要(适合)的东西。棒球、大猩猩、氦气球。

但您选择不这样做:您是一家铅笔公司,所以您放入货车的只是铅笔。如果你汉堡公司的朋友让你在面包车里放一些汉堡肉饼,你会拒绝:这辆面包车只能装铅笔。

因此,当您从货车中取出某样东西时,您知道这将是一支铅笔,因为您确保通过这些门进入的唯一物品是一支铅笔。


ArrayList&lt;T&gt; 也是如此:您可以在该 Object[] 中存储任何内容,但您不能:您只能在其中存储 Ts,因为您可以只能通过add(T)addAll(Collection&lt;? extends T&gt;) 方法向其中添加内容。

因此,将元素存储在Object[] 中而不是更具体的类型数组中并不重要:您将从ArrayList&lt;T&gt; 中获得的唯一内容 - 通过它的get() (等)方法 - 是 T 或其子类型之一的实例。

【讨论】:

  • 所以,这基本上是与编译器的交易,不是吗?如果我们不使用泛型而只使用Object 编译器可以让我们在那里放任何东西,并且如果我们将B 类型的变量认为它是A 类型,我们只会得到运行时异常?但既然交易存在,编译器允许我们避免直接类型转换,对吗?
  • 没错。泛型是一个完全编译时的构造。在运行时,它只是一个ArrayList,你可以在里面放你喜欢的东西。
  • 在许多书籍示例中,他们使用T 来存储变量或对数组的内容进行操作(ArrayList 也这样做)。但为什么?如果数组中给定的ObjectT,除了类声明之外,还有什么需要在类中的任何位置调用它T
  • 关于泛型的全部要点是省略强制转换。你可以在没有泛型的情况下做任何事情——而且,在 Java 5 之前,我们做到了。您只需要自己进行大量的显式转换,并解决转换为错误类型的问题。您可以在不引用T 的情况下做所有事情,但是在编译时验证类型安全性会更困难。所以,尽可能使用T而不是Object
  • “哪里可以”很重要,因为例如不能通过Array.newInstance(Class, int)方法创建List&lt;String&gt;[],因为没有Class&lt;List&lt;String&gt;&gt;作为参数传递,仅Class&lt;List&gt;(通过List.class)。而且您真的需要它,因为您可以将任何内容存储在 Object[] 中,并控制存储在数组中的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多