【发布时间】:2010-10-05 22:51:36
【问题描述】:
我偶尔听说过泛型,Java 做得不对。 (最近的参考,here)
请原谅我的经验不足,但是什么能让他们变得更好?
【问题讨论】:
我偶尔听说过泛型,Java 做得不对。 (最近的参考,here)
请原谅我的经验不足,但是什么能让他们变得更好?
【问题讨论】:
不好:
List<byte> 确实由 byte[] 支持,并且不需要装箱)好:
【讨论】:
最大的问题是 Java 泛型只是编译时的东西,你可以在运行时颠覆它。 C# 之所以受到称赞,是因为它进行了更多的运行时检查。 this post 中有一些非常好的讨论,它链接到其他讨论。
【讨论】:
Class 对象。
主要问题是 Java 在运行时实际上没有泛型。这是一个编译时特性。
当您在 Java 中创建泛型类时,他们使用一种称为“类型擦除”的方法来实际从类中删除所有泛型类型,并实质上将它们替换为 Object。泛型的最高版本是,只要它出现在方法体中,编译器就会简单地将转换插入到指定的泛型类型。
这有很多缺点。恕我直言,最大的问题之一是您不能使用反射来检查泛型类型。类型在字节码中实际上不是泛型的,因此不能作为泛型进行检查。
此处的差异概述:http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
【讨论】:
(1) 导致一些非常奇怪的行为。我能想到的最好的例子是。假设:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
然后声明两个变量:
MyClass<T> m1 = ...
MyClass m2 = ...
现在拨打getOtherStuff():
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
第二个的泛型类型参数被编译器剥离,因为它是原始类型(意味着未提供参数化类型),即使它与参数化类型没有任何关系。
我还会提到我最喜欢的 JDK 声明:
public class Enum<T extends Enum<T>>
除了通配符(这是一个混合包),我只是认为 .Net 泛型更好。
【讨论】:
public class Redundancy<R extends Redundancy<R>> ;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>> 起初可能看起来很奇怪/多余,但实际上非常有趣,至少在 Java/它的泛型的约束下。枚举有一个静态的values() 方法,它给出了一个类型为枚举的元素数组,而不是Enum,并且该类型由泛型参数确定,这意味着您需要Enum<T>。当然,这种类型只在枚举类型的上下文中才有意义,并且所有枚举都是Enum 的子类,因此您需要Enum<T extends Enum>。但是,Java 不喜欢将原始类型与泛型混合,因此为了保持一致性,Enum<T extends Enum<T>>。
我要抛出一个非常有争议的观点。泛型使语言复杂化,也使代码复杂化。例如,假设我有一个将字符串映射到字符串列表的映射。在过去,我可以简单地将其声明为
Map someMap;
现在,我必须将其声明为
Map<String, List<String>> someMap;
每次我将它传递给某个方法时,我都必须再次重复那个又长又长的声明。在我看来,所有额外的输入都会分散开发人员的注意力,并将他带出“区域”。此外,当代码中充满了大量杂乱无章的内容时,有时很难稍后再返回并快速筛选所有杂乱内容以找到重要的逻辑。
Java 作为最常用的最冗长的语言之一已经声名狼藉,而泛型只会增加这个问题。
对于所有这些额外的冗长,你真正买了什么?有多少次你真的遇到过有人将一个整数放入一个应该包含字符串的集合中,或者有人试图从一个整数集合中拉出一个字符串?在我 10 年构建商业 Java 应用程序的经验中,这从来都不是错误的主要来源。所以,我不确定你会因为额外的冗长而得到什么。这真的只是让我觉得额外的官僚包袱。
现在我将变得非常有争议。我认为 Java 1.4 中集合的最大问题是必须在任何地方进行类型转换。我认为这些类型转换是额外的、冗长的东西,它们与泛型有许多相同的问题。所以,例如,我不能只是这样做
List someList = someMap.get("some key");
我必须这样做
List someList = (List) someMap.get("some key");
当然,原因是 get() 返回一个 Object,它是 List 的超类型。因此,如果没有类型转换,就无法进行分配。再一次,想想这条规则真正给你带来了多少。根据我的经验,不多。
我认为如果 1) 它没有添加泛型,但 2) 允许从超类型到子类型的隐式转换,Java 会更好。让不正确的强制转换在运行时被捕获。那么我就可以简单地定义
Map someMap;
后来做
List someList = someMap.get("some key");
所有的麻烦都会消失,我真的不认为我会在我的代码中引入大量新的错误来源。
【讨论】:
它们是编译时而非运行时的另一个副作用是您不能调用泛型类型的构造函数。所以你不能用它们来实现一个泛型工厂......
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk++
【讨论】:
忽略整个类型擦除混乱,指定的泛型不起作用。
这样编译:
List<Integer> x = Collections.emptyList();
但这是一个语法错误:
foo(Collections.emptyList());
其中 foo 定义为:
void foo(List<Integer> x) { /* method body not important */ }
所以表达式类型是否检查取决于它是分配给局部变量还是方法调用的实际参数。这有多疯狂?
【讨论】:
在编译时检查 Java 泛型的正确性,然后删除所有类型信息(该过程称为类型擦除。因此,泛型 List<Integer> 将减少到它的 原始类型,非泛型List,可以包含任意类的对象。
这导致能够在运行时将任意对象插入到列表中,并且现在无法分辨哪些类型被用作泛型参数。后者反过来导致
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
【讨论】:
将泛型引入 Java 是一项艰巨的任务,因为架构师试图平衡功能、易用性以及与遗留代码的向后兼容性。不出所料,必须做出妥协。
有些人还认为 Java 的泛型实现将语言的复杂性提高到了无法接受的程度(参见 Ken Arnold 的“Generics Considered Harmful”)。 Angelika Langer 的 Generics FAQs 给出了一个很好的想法,说明事情会变得多么复杂。
【讨论】:
我希望这是一个 wiki,所以我可以添加到其他人...但是...
问题:
<?extend MyObject>[] 之类的事情,但我不被允许)【讨论】:
Java 在运行时不强制使用泛型,仅在编译时强制使用。
这意味着您可以做一些有趣的事情,例如将错误的类型添加到泛型集合中。
【讨论】:
Java 泛型仅在编译时使用,并被编译成非泛型代码。在 C# 中,实际编译的 MSIL 是通用的。这对性能有很大的影响,因为 Java 仍然在运行时强制转换。 See here for more.
【讨论】:
如果你听Java Posse #279 - Interview with Joe Darcy and Alex Buckley,他们会谈论这个问题。这还链接到 Neal Gafter 的一篇名为 Reified Generics for Java 的博客文章,上面写着:
很多人不满意 方式造成的限制 泛型是用 Java 实现的。 具体来说,他们不满意 泛型类型参数不是 物化:它们不可用 运行。泛型已实现 使用擦除,其中泛型类型 参数被简单地删除 运行时。
那篇博文引用了较早的条目Puzzling Through Erasure: answer section,它强调了要求中关于迁移兼容性的观点。
目标是向后提供 源和兼容性 目标代码,以及迁移 兼容性。
【讨论】:
泛型的问题在于,IMO 使 Java API 中的方法签名的阅读和理解难度增加了 10 倍,但在健壮性方面并没有太多收获。事实上,他们可能应该采取其他方式,通过方法调用完全摆脱编译时类型兼容性检查,并将其留给开发人员来解决或处理运行时异常。
【讨论】: