创建泛型集合数组

时间:2011-12-13 02:56:45

标签: java generics compiler-construction

实际上,问题应该是

Creating an array of generic anything.

为什么编译器不能处理它?<​​/ p>

以下内容将被标记为错误 - 无法创建通用数组。

List<MyDTO>[] dtoLists = {new ArrayList<MyDTO>(), anExistingDtoList};

要克服这个问题,我需要

List<MyDTO>[] dtoLists = (List<MyDTO>[])Array.newInstance(ArrayList.class, 2);
dtoLists[0] = new ArrayList<MyDTO>();
dtoLists[1] = anExistingDtoList;

那么,为什么编译器不能将第一种情况转换为第二种情况呢?

我确实认识到泛型是编译时确定的而不是运行时确定的,而数组是运行时确定的,因此需要确定类型才能创建数组。

编译器设计人员会遇到哪些技术/逻辑障碍会妨碍他们实现这一目标?

这个问题纯粹是哲学上的,关于语言正交性吗?如果是这样,这种行为将如何违反语言正交性?

这是一个复杂性的问题吗?解释复杂性。

我希望我的问题的答案能让我更好地了解java编译器在涉及泛型时的行为。

旁注: c&#39; mon停止触发快乐。答案Array of Generic List 不回答我的问题。为什么编译器不能自发地执行转换?

3 个答案:

答案 0 :(得分:8)

实际上Java确实为varargs创建了通用数组,所以你可以这样做

List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList);

@SafeVarargs
static <E> E[] array(E... array)
{
    return array;
}

至于为什么禁止显式通用数组创建,它与类型擦除有关。 (在上述解决方案中存在同样的问题,但由@SafeVarargs抑制)但是它有争议;有不同的方法来处理问题,编译器警告可能就足够了。但他们选择彻底禁止它,可能是因为阵列不再重要,因为我们拥有通用集合

答案 1 :(得分:3)

我知道,相对于此问题的解决方法,Array.newInstance()是一种昂贵的调用方法。 IIRC它使用本机方法来实例化数组,在其他反射中。我不能提供任何统计数据,但这似乎是一个足够好的理由,这样的功能不能被编译器自动替换,以便允许通用数组创建。特别是考虑到ArrayList等的存在,它似乎不是一个紧迫的问题。

答案 2 :(得分:1)

编译器可以自发地执行转换,它们只是指定不,因为通用数组的行为不像非通用数组。

请参阅10.5. Array Store Exception

  

对于类型为A[]的数组,其中A是引用类型,在运行时检查对数组组件的赋值,以确保分配的值可分配给成分

     

如果所分配的值的类型与组件类型不是分配兼容,则会抛出ArrayStoreException

     

如果数组的组件类型不可恢复,则Java虚拟机无法执行前一段中描述的存储检查。这就是为什么禁止使用具有不可恢复元素类型的数组创建表达式。

如果我们在其中添加了其他类型的List<MyDTO>[],则List不会抛出,因此它不会像数组一样运行。请注意引号中的最后一句:&#34;这就是为什么禁止使用具有不可恢复元素类型的数组创建表达式。&#34; 这就是原因,它是&#39; s指定如此。 (并且,为了记录,this reasoning has always existed,所以当问题在2011年发布时它就存在了。)

我们仍然可以这样做:

@SuppressWarnings({"unchecked","rawtypes"})
List<MyDTO>[] dtoLists = new List[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

或者这个:

@SuppressWarnings("unchecked")
List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

(除了静态检查参数类型之外,varargs是等价的:它creates a List[]suppresses warnings。)

现在,确定,规范可以更改为&#34;如果分配的值的类型与 的原始类型组件类型不是分配兼容的......&#34; ,但重点是什么?它可以在一些不寻常的情况下拯救一些角色,但是对于那些不了解其含义的人来说会禁止警告。

此外,the tutorial以及我所见过的其他典型解释并未证明对类型系统协变数组的理解程度如何。

例如,给出以下声明:

// (declaring our own because Arrays.fill is defined as
// void fill(Object[], Object)
// so the next examples would more obviously pass)
static <T> void fill(T[] arr, T elem) {
    Arrays.fill(arr, elem);
}

你知道这个编译吗?

// throws ArrayStoreException
fill(new String[1], new Integer(0));

这也编译了:

// doesn't throw ArrayStoreException
fill(dtoLists, new ArrayList<Float>());

在Java 8之前,我们可以通过给出以下声明来使对fill的调用失败:

static <T, U extends T> void fill(T[] arr, U elem) {...}

但这只是类型推断的一个问题,现在它可以正常工作&#34;盲目地将List<Float>放入List<MyDTO>[]

这称为堆污染。它可能导致ClassCastException稍后被抛出,可能与实际导致问题的操作完全无关。使用像List这样的通用容器的堆污染需要更明显的不安全操作,例如使用raw types,但在这里,我们可以隐式地导致堆污染而且没有任何警告。

通用数组(实际上,通常是数组)只在最简单的情况下给我们静态检查。

很明显,语言设计者认为不允许它们更好,而理解它们出现的问题的程序员可以抑制警告并绕过限制。