【问题标题】:type erasure in implementation of ArrayList in JavaJava中ArrayList实现中的类型擦除
【发布时间】:2014-04-11 04:04:27
【问题描述】:

我在Java Generics 上阅读了这篇文章,其中提到ArrayList 的构造函数看起来有点像这样:

class ArrayList<V> {
  private V[] backingArray;
  public ArrayList() {
    backingArray = (V[]) new Object[DEFAULT_SIZE]; 
  }
}

我无法理解编译器的类型擦除和类型检查是如何发生的,正如那里所解释的那样。我得到的一点是类型参数转换为Object 类型。

我想它是(将所有V 替换为Object),但这绝对是错误的。

class ArrayList<Object> {
      private Object[] backingArray;
      public ArrayList() {
        backingArray = (Object[]) new Object[DEFAULT_SIZE]; 
      }
}

它究竟是如何转换为Object 类型但仍保留V 的类型安全性的? 当我有ArrayList&lt;String&gt;ArrayList&lt;Integer&gt; 时,每个都有两个不同的类吗?如果没有,StringInteger的类型信息存放在哪里?

【问题讨论】:

  • 顺便说一句,集合类实际上并没有这样做。也许他们在写这篇文章的时候就这样做了,但现在不是了。

标签: java object generics arraylist constructor


【解决方案1】:

好问题。首先进行类型检查。如果一切都编译(即,在它提供编译类型安全之后)是否会发生类型擦除。

同样,很多事情都是作为类型擦除的一部分发生的,其中包括:-

1)Adding casts 
2) creating bridge methods

但类型检查是先完成的,一切都在以后发生

【讨论】:

    【解决方案2】:

    您的类型擦除版本不正确。类型参数声明不会被删除到Object,但只会删除它的用法。更具体地说:

    • 泛型类型的擦除是其对应的原始类型。所以,对于ArrayList&lt;V&gt;,它只是ArrayList
    • 类型参数的擦除是其最左边的界限。
    • 所有类型参数都被删除了。类型参数是您在实例化泛型类时使用的参数。因此,ArrayList&lt;Integer&gt; 将被替换为 ArrayList

    所以,正确的删除版本应该是:

    class ArrayList {
        private Object[] backingArray;
        public ArrayList() {
          backingArray = (Object[]) new Object[DEFAULT_SIZE]; 
        }
    }
    

    当我有 ArrayList 和 ArrayList 时,每个都有两个不同的类吗?

    不,从来都不是这样。编译器仅生成泛型类型或方法的一个字节码表示,并将泛型类型或方法的所有实例化映射到唯一表示。

    如果不是String和Integer的类型信息存放在哪里?

    当编译器执行类型擦除时,它会根据一些预定义的规则删除所有类型信息,偶尔添加所谓的桥接方法,并添加所需的所有必要类型转换.

    因此,例如,ArrayList&lt;Integer&gt;ArrayList&lt;String&gt; 的以下用法:

    ArrayList<Integer> list = new ArrayList<Integer>();
    list.add(1);
    int value = list.get(0);
    
    ArrayList<String> list2 = new ArrayList<String>();
    list.add("A");
    String value2 = list.get(0);
    

    会转换成有点像这样:

    ArrayList list = new ArrayList();
    list.add(1);
    int value = (Integer) list.get(0);
    
    ArrayList list2 = new ArrayList();
    list.add("A");
    String value2 = (String) list.get(0);
    

    进一步阅读:

    【讨论】:

    • 如果necessary castings 是由compiler 添加的,对于ArrayList&lt;Integer&gt;,我必须转换为Integer,对于ArrayList&lt;String&gt;,我必须转换为String,这意味着会有是两个不同的班级吗?
    • @brainstorm 不,没有两个不同的类。为什么你认为强制转换需要两个不同的类?
    • 演员表将被添加到使用它们的地方..不在类定义本身中。如果我使用主程序,将在我创建 ArrayList 实例的地方添加演员表,而不是在 ArrayList 字节码本身...
    • 关于您上面的正确擦除版本,String[] backingArray 是如何创建的,您正在投射(Object[]) new Object[DEFAULT_SIZE]; ,我无法理解
    • @brainstorm 这就是重点。 String[] 根本没有创建。泛型类本身没有关于类型参数的信息。后备数组仍然是Object[]。该列表仍会为您提供Object 参考。这就是为什么需要将 list.get(0) 转换为 String 的原因。
    【解决方案3】:

    您的第二个示例不正确。类型擦除并不意味着将所有内容全局转换为Object。正如你所猜测的,这几乎没有任何意义。相反,类型擦除的作用(字面意思)如下(借用自Jon Skeet):

    List<String> list = new ArrayList<String>();
    list.add("Hi");
    String x = list.get(0);
    

    这段代码被翻译成:

    List list = new ArrayList();
    list.add("Hi");
    String x = (String) list.get(0);
    

    注意String 的演员阵容,而不仅仅是普通的Object。类型擦除“擦除”泛型类型并将其中的所有对象转换为T。这是在不产生运行时成本的情况下添加一些编译时用户友好性的巧妙方法。然而,正如文章所声称的,这并非没有妥协。

    考虑以下示例:

    ArrayList<Integer> li = new ArrayList<Integer>();
    ArrayList<Float> lf = new ArrayList<Float>();
    

    它可能看起来不直观(或不正确),但li.getClass() == lf.getClass() 将评估为真。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 2012-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多