Answers:
坏:
List<byte>
确实有一个byte[]
作为,并且不需要装箱)好:
最大的问题是Java泛型仅是编译时的事情,您可以在运行时对其进行颠覆。C#之所以受到赞誉是因为它可以执行更多的运行时检查。这篇文章中有一些非常好的讨论,它可以链接到其他讨论。
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已经成为最常用的最冗长的语言之一,因此已经有不好的声誉,而泛型只会增加这个问题。
那么,对于这些额外的冗长性,您真正买了什么?在某人将一个Integer放入应该保存Strings的集合中,或者有人试图从Integers集合中拉出String时,您真的遇到了多少次问题?在我从事构建商业Java应用程序的10年经验中,这从来没有成为错误的主要来源。因此,我不太确定您要得到的额外详细信息。这确实让我感到额外的官僚包bag。
现在,我将要引起争议。我认为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");
所有的麻烦都将消失,我真的不认为我会在代码中引入大量新的错误源。
在编译时检查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");
忽略整个类型的擦除混乱,指定的泛型将不起作用。
这样编译:
List<Integer> x = Collections.emptyList();
但这是一个语法错误:
foo(Collections.emptyList());
foo定义为:
void foo(List<Integer> x) { /* method body not important */ }
因此,是否检查表达式类型取决于是否将其分配给局部变量还是方法调用的实际参数。那有多疯狂?
Java不会在运行时强制执行泛型,而仅在编译时强制执行。
这意味着您可以做一些有趣的事情,例如将错误的类型添加到通用Collection中。
Java泛型仅在编译时被编译成非泛型代码。在C#中,实际的已编译MSIL是通用的。这对性能有巨大影响,因为Java仍在运行时进行转换。看到这里更多。
如果您听Java Posse#279-采访Joe Darcy和Alex Buckley,他们会谈论这个问题。这也链接到Neal Gafter的博客文章Reified Generics for Java,内容为:
许多人对Java中实现泛型的方式所带来的限制感到不满意。具体来说,他们对通用类型参数未得到优化不满意:它们在运行时不可用。泛型使用擦除来实现,其中泛型类型参数在运行时被简单地删除。
该博客文章引用了一个较旧的条目,即“ Erasure Through Erasure:Answer”部分,其中强调了需求中的迁移兼容性。
目的是提供源代码和目标代码的向后兼容性,以及迁移兼容性。