问题 在Java中实现ArrayList时键入擦除


我正在读这篇文章 Java泛型 并且在那里提到了一个构造函数 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<String> 和 ArrayList<Integer> 每个都有两个不同的类?如果没有,那里的类型信息在哪里 String 和 Integer 被储存了?


9567
2018-03-07 20:08


起源

另外,集合类实际上并不这样做。也许他们在撰写文章时就已经这样做了,但现在已经没有了 - Radiodef


答案:


您的类型擦除版本不正确。类型参数声明不会被删除 Object 但只有它的用法被删除。进一步来说:

  • 擦除泛型类型是其对应的原始类型。因此对于 ArrayList<V>,这将是公正的 ArrayList
  • 类型参数的擦除是其最左边界限。
  • 并且所有类型参数都被删除了。类型参数是在实例化泛型类时使用的参数。所以, ArrayList<Integer> 将被取代 ArrayList

所以,正确的擦除版本将是:

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

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

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

如果没有存储String和Integer的类型信息的位置?

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

所以,例如,以下用法 ArrayList<Integer> 和 ArrayList<String>

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);

进一步阅读:


7
2018-03-07 20:22



如果 necessary castings 添加 compiler,为 ArrayList<Integer>,我必须投 Integer 并为 ArrayList<String> 我必须投 String,意思是会有两个不同的类? - brain storm
@brainstorm不,没有两个不同的类。为什么你认为铸造需要两个不同的类? - Rohit Jain
将在使用它们的地方添加强制转换。不在类定义本身。如果我使用的是主程序,则会在我创建ArrayList实例的地方添加强制转换,而不是在ArrayList字节码本身... - Kumar Abhinav
@brainstorm这就是重点。 String[] 根本没有创造。泛型类本身内部没有类型参数的信息。支持阵列仍然是一个 Object[]。列表仍然会给你回复 Object 参考。这就是为什么需要铸造它 list.get(0) 至 String。 - Rohit Jain
@brainstorm是的。 - Rohit Jain


你的第二个例子不正确。类型擦除并不意味着全局地将一切都投射到 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() 将评估为真。


3
2018-03-07 20:23





好问题。首先进行类型检查。如果所有内容都编译(即,在提供编译类型安全性之后),则会发生类型擦除。

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

1)Adding casts 
2) creating bridge methods

但是首先进行类型检查,一切都在以后进行


0
2018-03-07 20:12