it-swarm.cn

Java的泛型有什么问题?

我在该站点上看到过多次文章,这些文章谴责Java对泛型的实现。现在,我可以诚实地说,使用它们没有任何问题。但是,我没有尝试自己做一个通用类。那么,Java的通用支持又有什么问题呢?

50
Michael K

Java的通用实现使用 类型擦除 。这意味着您的强类型泛型集合在运行时实际上是Object类型。这有一些性能方面的考虑,因为这意味着在将基本类型添加到通用集合时必须将原始类型装箱。当然,编译时类型正确性的好处胜过类型擦除的普遍愚蠢和对后向兼容性的过分关注。

56
ChaosPandion

观察到已经提供的答案集中在Java语言,JVM和Java类库)的组合上。

就Java语言而言,Java的泛型没有错。如 C#vs Java泛型 中所述,Java的泛型在语言级别上相当不错1个

欠佳的是JVM不直接支持泛型,这会带来很多主要后果:

  • 类库中与反射相关的部分无法公开Java语言中可用的完整类型信息
  • 涉及一些性能损失

我想我所做出的区分是一个简单的问题,因为Java语言无处不在,以JVM为目标,Java Class Library作为核心库)。

1个 除了通配符外,通配符可能会导致类型推断在一般情况下无法确定。这是C#和Java)泛型之间的主要区别,根本没有经常提到。谢谢,锑。

26
Roman Starkov

通常的批评是缺乏具体化。也就是说,对象在运行时不包含有关其通用参数的信息(尽管该信息仍然存在于字段,方法,构造函数以及扩展的类和接口上)。这意味着您可以将_ArrayList<String>_强制转换为_List<File>_。如果您使用_ArrayList<String>_分配给Object引用,然后将其强制转换为_List<String>_,则编译器会向您发出警告,但也会警告您。好处是,使用泛型可能不应该进行转换,如果没有不必要的数据,当然还有向后兼容性,性能会更好。

有人抱怨您不能基于通用参数(void fn(Set<String>)void fn(Set<File>))重载。您需要改用更好的方法名称。请注意,此重载不需要修正,因为重载是静态的编译时问题。

原始类型不适用于泛型。

通配符和界限非常复杂。它们非常有用。如果Java首选的不可变性和告诉不要询问的接口),那么声明方泛型而非使用端泛型将更合适。

13
Tom Hawtin - tackline

Java泛型很烂,因为您不能执行以下操作:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

如果泛型实际上是泛型类参数,则无需在上面的代码中宰杀每个类即可使其正常工作。

10
davidk01
  • 缺少通用目的的通用参数的编译器警告使该语言毫无意义地变得冗长,例如public String getName(Class<?> klazz){ return klazz.getName();}

  • 泛型 不要与数组一起玩

  • 丢失的类型信息会使反射变成一团糟的铸件和胶带。

5
Steve B.

我认为其他答案在一定程度上说了这一点,但并不太清楚。泛型的问题之一是在反射过程中丢失了泛型类型。因此,例如:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

不幸的是,返回的类型无法告诉您arr的通用类型是String。这是一个细微的差异,但是很重要。由于arr是在运行时创建的,泛型类型会在运行时删除,因此您无法弄清楚。正如一些人说的ArrayList<Integer>看起来与ArrayList<String>从反思的角度来看。

这对于Generics的用户而言可能无关紧要,但是,我们要创建一个幻想框架,该框架使用反射来弄清关于用户如何声明实例的具体泛型类型的幻想。

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

假设我们想要一个泛型工厂来创建MySpecialObject的实例,因为这是我们为此实例声明的具体泛型类型。好的Factory类无法查询自己为此实例声明的具体类型,因为Java删除了它们。

在.Net的泛型中,您可以执行此操作,因为在运行时该对象知道其为泛型类型,因为编译器将其编译为二进制文件。随着擦除Java无法做到这一点。

5
chubbsondubs

我可以说一些关于泛型的 good 事情,但这不是问题。我可能会抱怨说它们在运行时不可用,无法与数组一起使用,但是有人提到过。

一个很大的心理烦恼:我偶尔会遇到无法使仿制药起作用的情况。 (数组是最简单的示例。)而且我无法弄清楚泛型是否不能胜任工作,或者我只是愚蠢。 我讨厌那个。泛型之类的东西应该一直有效。每隔一段时间我无法使用Java语言做我想做的事,我知道问题就在我身上,我知道如果我继续努力的话,我会到达那里最终。使用泛型时,如果我变得太执着,我会浪费很多时间。

但真正的问题是泛型增加了太多的复杂性,而获得的收益却很少。在最简单的情况下,它可以阻止我将Apple)添加到包含汽车的列表中。很好。但是如果没有泛型,此错误将在运行时很快地抛出ClassCastException,而浪费的时间很少。如果我如果添加了一个带有儿童座椅的儿童汽车,我是否需要一个编译时警告,该列表仅适用于带儿童座椅的汽车中包含黑猩猩的简单对象列表呢?.

通用代码可能包含许多单词和字符,并且会占用大量额外空间,并且需要花费更长的时间才能读取。我可以花很多时间使所有这些额外的代码正常工作。当这样做的时候,我会发疯。我也浪费了自己的几个小时或更长时间,我想知道是否还有其他人能够弄清楚代码。我希望能够将其交付给比自己不聪明或没有时间浪费的人进行维护。

另一方面(我有点受伤,需要保持一些平衡),使用简单的集合和映射进行添加,放置和获取时,这很好在编写时进行检查,通常不会增加代码 if 某人 else 写出集合或地图)。而且Java在这方面比C#更好。似乎我想使用的C#集合都不曾处理过泛型。(我承认我在集合中有奇怪的口味。)

1
RalphChapin