Java泛型真的这么笨拙吗?为什么?

Java泛型真的这么笨拙吗?为什么?,java,generics,Java,Generics,请容忍我一会儿。我知道这听起来很主观,也很有争议,但我发誓最后会有一个问号,这个问题实际上可以用一种客观的方式来回答 我来自.NET和C#背景,近年来一直被泛型与扩展方法相结合在许多.NET常见问题解决方案中所提供的语法优势所宠坏。使C#泛型如此强大的一个关键特性是,如果其他地方有足够的信息,编译器可以推断类型参数,因此我几乎不必写出它们。你不需要写很多行代码,就可以知道你在上面节省了多少击键次数。例如,我会写作 var someStrings = new List<string>(

请容忍我一会儿。我知道这听起来很主观,也很有争议,但我发誓最后会有一个问号,这个问题实际上可以用一种客观的方式来回答

我来自.NET和C#背景,近年来一直被泛型与扩展方法相结合在许多.NET常见问题解决方案中所提供的语法优势所宠坏。使C#泛型如此强大的一个关键特性是,如果其他地方有足够的信息,编译器可以推断类型参数,因此我几乎不必写出它们。你不需要写很多行代码,就可以知道你在上面节省了多少击键次数。例如,我会写作

var someStrings = new List<string>();
// fill the list with a couple of strings...
var asArray = someStrings.ToArray();
究竟为什么我必须实例化一个新的
字符串[]
,其中没有任何元素,它不会用于任何东西,以便Java编译器知道它是
字符串[]
,而不是我想要的任何其他类型的数组

我意识到重载看起来就是这样的,
toArray()
当前返回一个
对象[]
。但是当Java的这一部分被发明时,为什么会做出这样的决定呢?为什么这种设计比跳过返回
对象[]
.toArray()
完全重载,只使用返回
T[]
toArray()
更好?这是编译器中的一个限制,还是框架这一部分的设计者的想象中的一个限制,还是其他什么


你可能从我对最不重要的事情的极度兴趣中可以看出,我有一阵子没睡觉了…

那是因为类型擦除。请参阅Wikipedia中关于Java泛型的文章:泛型类型信息仅在编译时可用,它被编译器完全剥离,在运行时不存在

因此,
toArray
需要另一种方法来确定返回的数组类型

Wikipedia文章中提供的示例非常具有说明性:

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass())             // evaluates to true <<==
  System.out.println("Equal");
ArrayList li=new ArrayList();
ArrayList lf=新的ArrayList();
如果(li.getClass()==lf.getClass())//计算结果为true,则
toArray(String[])
部分存在,因为在Java 1.5中引入泛型之前存在两个
toArray
方法。当时,无法推断类型参数,因为它们根本不存在

Java非常注重向后兼容性,所以这就是为什么API的特定部分很笨拙的原因

整个类型擦除功能也是为了保持向后兼容性。为1.4编译的代码可以愉快地与包含泛型的较新代码交互

是的,这很笨拙,但至少它没有打破引入泛型时存在的巨大Java代码库

编辑:因此,作为参考,1.4 API如下:

Object[] toArray();
Object[] toArray(Object[] a);
Object[] toArray();
T[] toArray(T[] a);
1.5 API是这样的:

Object[] toArray();
Object[] toArray(Object[] a);
Object[] toArray();
T[] toArray(T[] a);
我不确定为什么可以更改1-arg版本的签名,但不能更改0-arg版本。这似乎是一个合乎逻辑的变化,但可能有一些复杂的地方,我错过了。或者他们只是忘记了


EDIT2:在我看来,在这种情况下,Java应该在可用的情况下使用推断类型参数,在推断类型不可用的情况下使用显式类型。但我怀疑,要真正将其包含在语言中是很棘手的。

我不能谈论JDK团队的设计决策,但Java泛型的许多笨拙本质来自于泛型是Java 5的一部分(1.5-随便什么)这一事实。JDK中有很多方法都因试图保持与1.5之前的API的向后兼容性而受到影响

至于繁琐的
List strings=new ArrayList()
语法,我敢打赌这是为了保留语言的迂腐本质。声明,而不是推断,是Java的前进顺序


strings.toArray(新字符串[0])?无法推断类型,因为数组本身被视为泛型类(
Array
,如果已公开)

总而言之,Java泛型的存在主要是为了保护您在编译时避免键入错误。您仍然可以在运行时使用错误的声明来攻击自己,而且语法很麻烦。通常情况下,泛型使用的最佳实践是您所能做到的最佳实践。

其他人已经回答了“为什么”的问题(我更喜欢Cameron Skinner的答案),我要补充的是,您不必每次都实例化一个新数组,它也不必为空。如果数组足够大,可以容纳集合,则它将用作返回值。因此:

String[]asArray=someStrings.toArray(新字符串[someStrings.size()])

将仅分配一个大小正确的数组,并使用集合中的元素填充该数组

此外,一些Java集合实用程序库包括静态定义的空数组,可以安全地用于此目的。例如,请参见Apache Commons

编辑:


在上面的代码中,当集合为空时,实例化的数组实际上是垃圾。由于数组在Java中无法调整大小,空数组可以是单例数组。因此,从库中使用一个常量空数组可能会稍微高效一些。

根据Neal Gafter的说法,SUN没有像MS那样的足够资源

SUN推出了带有类型擦除的Java5,因为将泛型类型信息提供给运行时意味着更多的工作。他们不能再拖延了


注意:向后兼容性不需要类型擦除-该部分由原始类型处理,这与可重新定义的类型是不同的概念。Java仍然有机会删除类型擦除,即使所有类型都可重新定义;然而,它并没有被认为是在任何可预见的议程上的紧迫性。

Java泛型实际上是不存在的
Object[] toArray();
Object[] toArray(Object[] a);
Object[] toArray();
T[] toArray(T[] a);
T[] toArray() {
    // what would you put here?
}