为什么有些人声称Java的泛型实现很糟糕?

时间:2009-02-06 14:45:52

标签: java generics

我偶尔听说过,通用泛型,Java并没有把它弄好。 (最近参考,here

原谅我的经验不足,但是什么会让他们变得更好?

13 个答案:

答案 0 :(得分:139)

为:

  • 类型信息在编译时丢失,因此在执行时你无法确定它的“意味着什么类型”
  • 不能用于值类型(这是一个很大的问题 - 在.NET中,List<byte>实际上由byte[]支持,并且不需要装箱)
  • 调用泛型方法的语法很糟糕(IMO)
  • 约束的语法可能会令人困惑
  • 通配令一般令人困惑
  • 由于上述铸造等各种限制

好:

  • 通配符允许在呼叫方指定协方差/逆变,在许多情况下非常简洁
  • 总比没有好!

答案 1 :(得分:26)

最大的问题是Java泛型只是一个编译时的东西,你可以在运行时破坏它。 C#受到称赞,因为它进行了更多的运行时检查。在this post中有一些非常好的讨论,它与其他讨论有关。

答案 2 :(得分:15)

主要问题是Java在运行时实际上没有泛型。这是一个编译时功能。

当您在Java中创建泛型类时,他们使用一个名为“Type Erasure”的方法来实际删除类中的所有泛型类型,并基本上用Object替换它们。泛型版本的泛型是编译器只要在方法体中出现时,只需将强制转换插入指定的泛型类型。

这有很多缺点。恕我直言,最大的一个就是你不能使用反射来检查泛型类型。类型在字节代码中实际上不是通用的,因此不能作为泛型进行检查。

这里的差异很好概述:http://www.jprl.com/Blog/archive/development/2007/Aug-31.html

答案 3 :(得分:14)

  1. 运行时实现(即不是类型擦除);
  2. 使用原始类型的能力(这与(1)相关);
  3. 虽然通配符很有用,但语法和知道何时使用它会让很多人感到困惑。和
  4. 没有性能提升(因为(1); Java泛型是铸造对象的语法糖)。
  5. (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(); 
    

    第二个版本的编译器剥离了它的泛型类型参数,因为它是一个原始类型(意味着没有提供参数化类型),即使它没有 nothing 与参数化类型相关。

    我还会提到我最喜欢的JDK声明:

    public class Enum<T extends Enum<T>>
    

    除了通配符(这是一个混合包)我认为.Net泛型更好。

答案 4 :(得分:9)

我要抛出一个非常有争议的观点。泛型使语言复杂化并使代码复杂化。例如,假设我有一个将字符串映射到字符串列表的映射。在过去,我可以简单地将其声明为

Map someMap;

现在,我必须将其声明为

Map<String, List<String>> someMap;

每次我把它传递给某种方法时,我都要重复那个重要的长篇宣言。在我看来,所有额外的打字都会分散开发人员的注意力并将他带出“区域”。此外,当代码充满了大量的内容时,有时很难再回到它上面并快速筛选出所有的内容以找到重要的逻辑。

Java作为常用的最冗长的语言之一已经声名狼借,而泛型只是增加了这个问题。

你真正为那些额外的冗长而买什么?有多少次你有一个问题,有人把一个Integer放入一个应该保存字符串的集合中,或者有人试图从一个整数集合中提取一个字符串?在我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");

所有的瑕疵都会消失,我真的不认为我会在我的代码中引入一个新的错误来源。

答案 5 :(得分:7)

它们是编译时而不是运行时的另一个副作用是你不能调用泛型类型的构造函数。因此,您无法使用它们来实现通用工厂......


   public class MyClass {
     public T getStuff() {
       return new T();
     }
    }

- jeffk ++

答案 6 :(得分:6)

在编译时检查Java泛型的正确性,然后删除所有类型信息(该过程称为 type erasure 。因此,泛型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");

答案 7 :(得分:5)

忽略整个类型的擦除混乱,指定的泛型不起作用。

编译:

List<Integer> x = Collections.emptyList();

但这是语法错误:

foo(Collections.emptyList());

foo定义为:

void foo(List<Integer> x) { /* method body not important */ }

因此,表达式类型检查是否取决于它是分配给局部变量还是方法调用的实际参数。这有多疯狂?

答案 8 :(得分:4)

将泛型引入Java是一项艰巨的任务,因为架构师正在尝试平衡功能,易用性以及与遗留代码的向后兼容性。相当期待,必须做出妥协。

有些人也认为Java的泛型实现会将语言的复杂性提高到不可接受的程度(参见Ken Arnold的“Generics Considered Harmful”)。 Angelika Langer的Generics FAQs给出了一个很好的想法,即事情会变得多么复杂。

答案 9 :(得分:4)

我希望这是一个维基,所以我可以添加到其他人......但是......

问题:

  • 类型擦除(无运行时可用性)
  • 不支持主要类型
  • 与Annotations不兼容(它们都是在1.5中添加的我仍然不确定为什么注释除了使用特征之外不允许泛型)
  • 与数组不兼容。 (有时候我真的想做类<之类的东西?扩展MyObject > [],但我不被允许)
  • Wierd通配符语法和行为
  • 通用支持在Java类中不一致的事实。他们将它添加到大多数集合方法中,但是每隔一段时间就会遇到一个不在那里的实例。

答案 10 :(得分:3)

Java不会在运行时强制执行泛型,只能在编译时执行。

这意味着你可以做一些有趣的事情,比如在通用集合中添加错误的类型。

答案 11 :(得分:2)

Java泛型仅是编译时,并编译为非泛型代码。在C#中,实际编译的MSIL是通用的。这对性能有很大的影响,因为Java仍然在运行时投射。 See here for more

答案 12 :(得分:2)

如果你听Java Posse #279 - Interview with Joe Darcy and Alex Buckley,他们会谈论这个问题。这也链接到标题为Reified Generics for Java的Neal Gafter博客文章:

  

很多人对此不满意   方式造成的限制   泛型是用Java实现的。   具体来说,他们不高兴   泛型类型参数不是   具体化:他们不在   运行。仿制药已经实施   使用擦除,其中通用类型   参数简单地删除   运行时。

该博客文章引用了较旧的条目Puzzling Through Erasure: answer section,强调了要求中迁移兼容性的要点。

  

目标是提供倒退   源和的兼容性   目标代码,以及迁移   兼容性。