为什么我们可以使用带通用引用的数组

时间:2012-03-27 09:07:39

标签: java generics java-ee scjp

在回答有关此问题的问题时:https://stackoverflow.com/a/9872630/82609

我尝试执行以下操作:

Comparator<String>[] comparators = new Comparator[] {...};

有效!但以下情况并非如此:

Comparator<String>[] comparators = new Comparator<String>[] {...};

在相关问题上,我做出了假设:

  

我想这是因为最初阵列合同可能是某种东西   像这样:

     
    

如果您创建一个X类型的数组,您将永远无法放置     其中的任何东西都是非 - 非X.如果你尝试,你将得到一个ArrayStoreException

  
     

因此,允许使用泛型创建的数组会导致不同   规则如:

     
    

如果您创建一个类型为X<Y>的数组,您将永远无法做到     放任何不是X的东西。如果你尝试,你会得到一个     ArrayStoreException信息。但是由于类型擦除,您可以添加X<Y>X<Z>个对象!

  

但是考虑一下,这真的是一个问题:

Comparator<String>[] comparators = new Comparator<String>[] {...};

我真的不明白为什么不可能,因为使用这样的东西会:

  • 检查在运行时插入的类
  • 检查编译时插入的类类型

最后我们可以使用具有泛型类型引用的数组,并且由于不可能创建具有泛型类型的数组,我想很多人甚至不知道它是可能的。

我只是想知道是否有人知道这个选择背后的原因?

这有点像强迫人们使用List<String> = new ArrayList();而非使用List<String> = new ArrayList<String>();


你在约书亚布洛赫着名的书中给出了一个很好的例子。 正如你/他解释的那样,使用泛型数组+协方差是很危险的,并且可能导致ClassCastException,而我们期望使用协方差从数组中得到ArrayStoreException。

但请注意以下内容仍然合法且导致相同:

List<String>[] stringLists = new List[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);

但是,它会在编译时生成未经检查的强制转换警告,正如您所提到的,在运行时会产生ClassCastException。

2 个答案:

答案 0 :(得分:4)

我知道你来自哪里(从实际意义上说我基本同意),但我认为存在差异会激发当前形势。

正如您所提到的,擦除意味着通用参数在运行时不可用,因此在编译时检查类型(对于List<String>或您的Comparator<String>[])。重要的是,这是基于变量的通用参数。

另一方面,数组在运行时检查它们的参数类型,当它们被插入时,如果它们被误用,它们可以抛出ArrayStoreException(通常是由于滥用它们的协方差)。因此,数组需要能够在内部执行两个项目符号点检查,当然它们无法在运行时检查泛型参数。因此,实例化通用数组是没有意义的,因为数组必须完全忽略泛型参数,这最多会产生误导。

也就是说,将这样的数组分配给参数化引用是有意义的,因为编译器可以执行泛型检查。而且您认为这涵盖了所有基础,并确保检查泛型类型(只要变量参数化正确),您就是正确的。

这个选择背后的底线原因,以及为什么数组在这方面与集合不同,是数组需要在插入时实际检查它们的参数类型,而集合只需要你的意思并允许类型错误稍后传入ClassCastException

答案 1 :(得分:1)

引用伟大的Effective Java Second Edition页面120:

为什么通用数组创建是非法的 - 不会编译!

List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)

让我们假装创建通用数组的第1行是合法的。第2行创建和 初始化包含单个元素的List<Integer>。 3号线存储了 List<String>数组成一个Object数组变量,这是合法的,因为数组 是协变的。第4行将List<Integer>存储到the的唯一元素中 对象数组成功,因为泛型是通过擦除实现的: List<Integer>实例的运行时类型只是List,而运行时类型是 List<String>[]实例为List[],因此此分配不会生成 ArrayStoreException。现在我们遇到了麻烦。我们存储了List<Integer> 将instance转换为声明仅包含List<String>个实例的数组。在 第5行,我们从这个数组中的唯一列表中检索唯一元素。编译器 自动将检索到的元素强制转换为String,但它是一个Integer,所以我们得到了 运行时ClassCastException。为了防止这种情况发生,第1行 (创建通用数组)会生成编译时错误。