Java的泛型有什么问题?[关闭]


49

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




克林顿开始(以下简称“iBatis的家伙”)说,“他们没有工作......”
蚊蚋

Answers:


54

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


4
只是要添加,由于类型擦除,除非您使用TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…之
Jeremy Heiler

43
意思是new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
请注意-想起一个名字,2010年

9
@Job不,不是。C ++使用完全不同的方法进行元编程,称为模板。它使用鸭式输入法,并且您可以做一些有用的事情,template<T> T add(T v1, T v2) { return v1->add(v2); }并且您有一种真正通用的方式来创建一个函数,该函数可以使add两件事变得无关紧要,无论它们是什么,它们都必须有一个add()用一个参数命名的方法。
特立尼达

7
@Job确实是生成的代码。模板旨在代替C预处理器,并且这样做,它们应该能够做出一些非常巧妙的技巧,并且它们本身就是图灵完备的语言。Java泛型只是用于类型安全容器的糖,而C#泛型更好,但仍然是穷人的C ++模板。
特立尼达

3
@Job AFAIK,Java泛型不会为每个新的泛型类型生成一个类,它只是将泛型添加到使用泛型方法的代码中,因此它实际上不是对IMO进行元编程。C#模板确实会为每种泛型类型生成新代码,也就是说,如果您在C#的世界中使用List <int>和List <double>将为它们中的每一个生成代码。与模板相比,C#泛型仍然需要知道您可以提供哪种类型您不能实现Add我给出的简单示例,无法事先不知道可以将其应用于哪些类,这是一个缺点。
特立尼达

26

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

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

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

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

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

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


14
JVM不需要支持泛型。这与说C ++没有模板是相同的,因为ix86真实机器不支持模板,这是错误的。问题在于编译器采用哪种方法来实现泛型类型。再说一次,C ++模板在运行时不会带来任何损失,根本没有做任何反思,我想在Java中需要做的唯一原因仅仅是糟糕的语言设计。
特立尼达

2
@Trinidad,但我没有说Java没有泛型,所以我看不出它有什么相同之处。是的,JVM 不需要支持它们,就像C ++ 不需要优化代码一样。但是不对其进行优化肯定会被认为是“出问题了”。
罗曼·斯塔科夫

3
我认为,JVM无需直接支持泛型就可以在其上使用反射。其他人已经做到了,所以可以做到,问题是Java不会从泛型(没有验证的东西)生成新类。
特立尼达

4
@Trinidad:在C ++中,假设编译器有足够的时间和内存,则模板可以用来执行任何事情,并且可以利用编译时可用的信息来完成所有事情(因为模板的编译是图灵完成的),但是无法使用程序运行前不可用的信息来创建模板。相比之下,在.net中,程序可以根据输入创建类型。编写一个程序,其中可以创建的不同类型的数量超过宇宙中电子的数量,这将是相当容易的。
2012年

2
@Trinidad:显然,程序的任何执行都无法创建所有这些类型(或者,实际上,除了它们的无穷小部分之外的任何类型),但是重点是不必这样做。它只需要创建实际使用的类型。一个人可以拥有一个程序,该程序可以接受任何任意数字(例如8675309),并从中创建一个对该数字唯一的类型(例如Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>),该类型的成员将与其他任何类型的成员不同。在C ++中,必须从任何输入生成的所有类型都必须在编译时生成。
2012年

13

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

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

基本类型不适用于泛型。

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


“请注意,这种重载不需要修正,因为重载是一个静态的编译时问题。” 不是吗 如果不进行修改,它将如何运作?为了知道要调用哪种方法,JVM是否不必在运行时知道您拥有哪种类型的set?
MatrixFrog 2011年

2
@MatrixFrog不。正如我所说,重载是编译时的问题。编译器使用表达式的静态类型来选择特定的重载,然后将重载方法写入类文件。如果有,Object cs = new char[] { 'H', 'i' }; System.out.println(cs);您会胡说八道。变更的类型cschar[],你会得到Hi
汤姆·霍汀-大头钉2011年

10

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
      */
    }
}

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


您无需全力以赴。您只需要添加一个合适的工厂类并将其注入到AsyncAdapter中即可。(从根本上讲,这与泛型无关,而是构造函数不是在Java中继承的事实)。
彼得·泰勒

13
@PeterTaylor:合适的工厂班级只会把所有样板推到其他看不见的地方,但这仍然不能解决问题。现在,每次我想实例化一个新实例时,Parser都需要使工厂代码更加复杂。如果“泛型”的真正含义是泛型的,那么就不需要工厂和所有其他以设计模式命名的废话了。这是我更喜欢使用动态语言的众多原因之一。
2011年

2
有时在C ++中类似,在使用模板+虚拟工具时,您无法传递构造函数的地址-您只能使用工厂函子来代替。
Mark K Cowan '18

Java很烂,因为您不能在方法名称前写“受保护”?可怜的你。坚持使用动态语言。并特别警惕Haskell。
fdreger

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

  • 泛型不能很好地与数组配合使用

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


当收到使用HashMap而不是的警告时,我确实很烦HashMap<String, String>
Michael

1
@Michael,但实际上应该与泛型一起使用...
替代

阵列问题涉及可验证性。有关说明,请参见Java泛型书(Alligator)。
ncmathsadist 2012年

5

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

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无法做到这一点。


1

我可以说一些关于泛型的东西,但这不是问题。我可能会抱怨说它们在运行时不可用,并且不能与数组一起使用,但这已被提及。

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

但是真正的问题是,泛型增加了太多的复杂性,而带来的收益却很少。在最简单的情况下,它可以阻止我将苹果添加到包含汽车的列表中。精细。但是如果没有泛型,此错误将在运行时非常快地抛出ClassCastException,而浪费的时间很少。如果我添加了带儿童座椅和儿童座椅的汽车,我是否需要一个编译时警告,该列表仅适用于带儿童座椅和黑猩猩的汽车?普通对象实例的列表开始看起来是个好主意。

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

另一方面(我在这里感到有些不适,觉得需要提供一些平衡),当使用简单的集合和映射在编写时进行添加,放置和获取检查时,这很好,并且通常不会增加太多复杂的代码如果 有人 别人 写的集合或地图)。Java比C#更好。似乎我想使用的所有C#集合都没有处理过泛型。(我承认我的收藏品口味怪异。)


不错的咆哮。但是,有许多库/框架在没有泛型的情况下可能存在,例如Guice,Gson,Hibernate。一旦您习惯了泛型,它就没有那么难了。键入和读取类型实参是真正的PITA。如果可以使用,这里val会有所帮助。
maaartinus 2014年

1
@maaartinus:前不久写了。OP也要求“问题”。我发现泛型非常有用并且非常喜欢它们-现在比那时更多了。但是,如果您正在编写自己的收藏集,则很难学习它们。当在运行时确定集合的类型时(当集合具有告诉您其条目的类的方法时),它们的作用就会减弱。此时,泛型不起作用,您的IDE将产生数百个毫无意义的警告。99.99%的Java编程不涉及此。但是我只是很多的这个麻烦,当我写上面。
RalphChapin 2014年

作为Java和C#开发人员,我想知道为什么您会更喜欢Java集合?
Ivaylo Slavov

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.这实际上取决于在程序启动和到达相关代码行之间需要多少运行时间。如果用户报告了错误,并且需要几分钟(或者,在最坏的情况下,可能需要数小时甚至数天)才能重现,则编译时验证的外观会越来越好……
Mason Wheeler 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.