2011-12-13 102 views
20

数组其实,问题应该是创建泛型集合

Creating an array of generic anything. 

为什么不能编译照顾呢?

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

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编译器在涉及泛型时的行为。

附注: 来吧,停止被触发快乐。答案Array of Generic List 不回答我的问题。为什么编译器不能自发地执行转换?

+0

[Array of Generic List](http://stackoverflow.com/questions/7810074/array-of-generic-list) – Thilo

+1

的可能重复检查出该重复的最佳答案。它有一个例子,为什么这是不被允许的。 – Thilo

+2

这不是重复的。我的问题要求有关编译器设计问题的答案。 –

回答

8

其实Java那样创建可变参数的泛型数组,所以你可以做

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

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

至于为什么是明确的泛型数组创建禁止的,它是与类型擦除。 (同样的担忧存在于上述解决方案中,但被@SafeVarargs压制)然而,这是值得商榷的;有不同的方法来处理这个问题,编译器警告可能就足够了。但他们选择了彻底禁止它,可能是因为数组都不再重要反正现在我们已经泛型集合

+0

作为一名C++程序员,Java中的数组对我非常有用。在Java中,我无法传递指针。我必须在对象或数组中包含一个变量,以便像指针一样传递它。 –

+0

我的意思是,在泛型之前,我们不能'列出',因此为静态类型设置'Foo []'很重要。这个重要性已经消失。当然,数组仍然是重要的基本构建块。 – irreputable

3

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

1

编译器可以自发地进行转换,他们只是规定不因为通用阵列不能表现像非泛型数组。

参见10.5. Array Store Exception

对于数组类型为A[],其中A是引用类型,分配到该阵列的一个组件在运行时进行检查,以确保所分配的值是分配到组件。

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

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

如果我们把一些其他种类的List在它List<MyDTO>[]不会丢,所以它不表现为一个数组。请注意报价中的最后一句:“这就是为什么禁止使用不可指定元素类型的数组创建表达式。”这就是原因,它被指定为这样。 (而且,备案,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 
}; 

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

现在,当然,规范可以改变“如果所分配的值的类型与的原始类型的元件类型...”不是分配兼容的,但是要点是什么?它会在一些不寻常的情况下保存少数人物,但是否则会禁止那些不了解其含义的人发出警告。

此外,我看到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)); 

而这个编译过:

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

但是,这只是一个:

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

的Java 8之前,我们可以到fill失败给它下面的声明使这些电话类型推断问题,现在它工作“正确”,一味地将List<Float>放入List<MyDTO>[]

这叫做堆污染。它可能会导致ClassCastException在某个时间后抛出,可能与实际导致问题的操作完全无关。像List这样的通用容器造成的堆污染需要更加明显的不安全行为,例如使用raw types,但在这里,我们可以隐式导致堆污染,并且不会有任何警告。

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

因此很明显,语言设计者认为最好不要让他们,而了解他们存在问题的程序员可以抑制警告并绕过限制。