创建generics集合数组
实际上,问题应该是
Creating an array of generic anything.
为什么编译器不能处理它呢?
以下内容将被标记为错误 – 无法创建通用数组。
List[] dtoLists = {new ArrayList(), anExistingDtoList};
要克服这一点,我需要
List[] dtoLists = (List[])Array.newInstance(ArrayList.class, 2); dtoLists[0] = new ArrayList(); dtoLists[1] = anExistingDtoList;
那么,为什么编译器不能将第一种情况转换为第二种情况呢?
我确实认识到generics是编译时确定的而不是运行时确定的,而数组是运行时确定的,因此需要确定类型才能创建数组。
编译器设计人员会遇到哪些技术/逻辑障碍会妨碍他们实现这一目标?
关于语言正交性,问题纯粹是哲学吗? 如果是这样,这种行为将如何违反语言正交性?
这是复杂性的问题吗? 解释复杂性。
我希望我的问题的答案能让我更好地了解java编译器在涉及generics时的行为。
旁注:来吧,停止触发快乐。 答案通用列表数组不回答我的问题。 为什么编译器无法自发执行转换?
实际上Java确实为varargs创建了通用数组,所以你可以这样做
List[] dtoLists = array(new ArrayList (), anExistingDtoList); @SafeVarargs static E[] array(E... array) { return array; }
至于为什么禁止显式通用数组创建,它与类型擦除有关。 (上述解决方案存在同样的问题,但@SafeVarargs
抑制了这种情况)但是它有争议; 有不同的方法来处理问题,编译器警告可能就足够了。 但是他们选择彻底禁止它,可能是因为我们拥有通用集合,因此arrays不再重要
我知道,相对于这个问题的解决方法, Array.newInstance()
是一种昂贵的调用方法。 IIRC它使用本机方法来实例化数组,在其他reflection中。 我不能提供任何统计数据,但这似乎是一个足够好的理由,这样的function不能被编译器自动替换,以便允许通用数组创建。 特别是考虑到ArrayList
等的存在,它似乎不是一个紧迫的问题。
编译器可以自发地执行转换,只是指定它们,因为generics数组不能像非generics数组那样运行。
见10.5。 数组存储exception :
对于类型为
A[]
的数组,其中A
是引用类型,在运行时检查对数组组件的赋值,以确保分配的值可分配给组件。如果分配的值的类型与组件类型不是分配兼容的,则抛出
ArrayStoreException
。如果数组的组件类型不可重新生成,则Java虚拟机无法执行前一段中描述的存储检查。 这就是禁止使用具有不可恢复元素类型的数组创建表达式的原因。
如果我们在其中放入其他类型的List
,则List
不会抛出,因此它不像数组那样。 请注意引号中的最后一句: “这就是为什么禁止使用具有不可恢复元素类型的数组创建表达式的原因。” 这就是原因,它被指定为如此。 (并且,为了记录, 这种推理一直存在 ,所以当问题在2011年发布时它就出现了。)
我们仍然可以这样做:
@SuppressWarnings({"unchecked","rawtypes"}) List[] dtoLists = new List[] { new ArrayList (), anExistingDtoList };
或这个:
@SuppressWarnings("unchecked") List[] dtoLists = (List []) new List>[] { new ArrayList (), anExistingDtoList };
(除了静态检查参数类型之外,varargs事物是等价的:它创建一个List[]
并禁止警告 。)
现在,当然,规范可以更改为“如果所分配的值的类型与组件类型的原始类型不是分配兼容的 ……” ,但有什么意义呢? 它可以在一些不寻常的情况下保存一些角色,但是对于那些不理解其含义的人则会禁止警告。
此外,我所看到的教程和其他典型解释没有certificate的是对类型系统协变数组的影响。
例如,给出以下声明:
// (declaring our own because Arrays.fill is defined as // void fill(Object[], Object) // so the next examples would more obviously pass) static 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());
在Java 8之前,我们可以通过给它以下声明来使这些调用fill
失败:
static void fill(T[] arr, U elem) {...}
但这只是类型推断的问题,现在它“正确”工作,盲目地将List
放入List
。
这称为堆污染 。 它可能导致稍后抛出ClassCastException
,可能与实际导致问题的操作完全无关。 使用像List
这样的通用容器的堆污染需要更明显的不安全操作,例如使用原始类型 ,但在这里,我们可以隐式地导致堆污染而没有任何警告。
通用数组(实际上,通常是数组)只在最简单的情况下给我们静态检查。
所以很明显,语言设计者认为不允许它们更好,而理解它们存在的问题的程序员可以抑制警告并绕过限制。