我在该站点上看到过多次文章,这些文章谴责Java对泛型的实现。现在,我可以诚实地说,使用它们没有任何问题。但是,我没有尝试自己做一个通用类。那么,Java的通用支持又有什么问题呢?
我在该站点上看到过多次文章,这些文章谴责Java对泛型的实现。现在,我可以诚实地说,使用它们没有任何问题。但是,我没有尝试自己做一个通用类。那么,Java的通用支持又有什么问题呢?
Answers:
Java的通用实现使用类型擦除。这意味着您的强类型泛型集合实际上Object
在运行时是类型。这有一些性能方面的考虑,因为这意味着在将原始类型添加到通用集合时必须将原始类型装箱。当然,编译时类型正确性的好处胜过类型擦除的普遍愚蠢和对后向兼容性的过分关注。
TypeLiteral
google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…之
new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
并且您有一种真正通用的方式来创建一个函数,该函数可以使add
两件事变得无关紧要,无论它们是什么,它们都必须有一个add()
用一个参数命名的方法。
Add
我给出的简单示例,无法事先不知道可以将其应用于哪些类,这是一个缺点。
观察到已经提供的答案集中在Java语言,JVM和Java类库的组合上。
就Java语言而言,Java的泛型没有错。如C#与Java泛型中所述,Java的泛型在语言级别1上相当不错。
欠佳的是JVM不直接支持泛型,这会带来很多主要后果:
我想我所做出的区分是一个简单的问题,因为Java语言无处不在,以JVM为目标,以Java类库为核心库。
1除了通配符外,通配符可能会导致类型推断在一般情况下无法确定。这是C#和Java泛型之间的一个主要区别,而这根本很少被提及。谢谢,锑。
8675309
),并从中创建一个对该数字唯一的类型(例如Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
),该类型的成员将与其他任何类型的成员不同。在C ++中,必须从任何输入生成的所有类型都必须在编译时生成。
通常的批评是缺乏具体化。也就是说,对象在运行时不包含有关其通用参数的信息(尽管该信息仍然存在于字段,方法,构造函数以及扩展的类和接口上)。这意味着您可以将转换ArrayList<String>
为List<File>
。编译器会向您发出警告,但是如果您将其ArrayList<String>
分配给Object
引用,然后将其强制转换为,它也会警告您List<String>
。好处是,使用泛型可能不应该进行转换,如果没有不必要的数据,当然还有向后兼容性,性能会更好。
有人抱怨您不能基于通用参数(void fn(Set<String>)
和void fn(Set<File>)
)重载。您需要改用更好的方法名称。请注意,此重载不需要修正,因为重载是静态的编译时问题。
基本类型不适用于泛型。
通配符和界限非常复杂。它们非常有用。如果Java首选不可变性和“不要问”接口,那么声明方而不是使用方泛型将更为合适。
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
您会胡说八道。变更的类型cs
来char[]
,你会得到Hi
。
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
*/
}
}
如果泛型实际上是泛型类参数,则无需在上面的代码中宰杀每个类即可使其正常工作。
Parser
都需要使工厂代码更加复杂。如果“泛型”的真正含义是泛型的,那么就不需要工厂和所有其他以设计模式命名的废话了。这是我更喜欢使用动态语言的众多原因之一。
缺少通用性的通用参数丢失的编译器警告使该语言毫无意义地变得冗长,例如 public String getName(Class<?> klazz){ return klazz.getName();}
丢失的类型信息会使反射变成一团糟的铸件和胶带。
HashMap
而不是的警告时,我确实很烦HashMap<String, String>
。
我认为其他答案在一定程度上说了这一点,但并不太清楚。泛型的问题之一是在反射过程中丢失了泛型类型。因此,例如:
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无法做到这一点。
我可以说一些关于泛型的好东西,但这不是问题。我可能会抱怨说它们在运行时不可用,并且不能与数组一起使用,但这已被提及。
一个很大的心理烦恼: 我偶尔会遇到无法使用仿制药的情况。(数组是最简单的示例。)而且我无法弄清楚泛型是否不能胜任工作,或者我只是愚蠢。 我讨厌那个。 泛型之类的东西应该总是可以工作。每当我无法使用Java语言完成我想做的事情时,我都知道问题出在我身上,而且我知道如果我继续努力,我最终会到达那里。使用泛型时,如果我变得太执着,我会浪费很多时间。
但是真正的问题是,泛型增加了太多的复杂性,而带来的收益却很少。在最简单的情况下,它可以阻止我将苹果添加到包含汽车的列表中。精细。但是如果没有泛型,此错误将在运行时非常快地抛出ClassCastException,而浪费的时间很少。如果我添加了带儿童座椅和儿童座椅的汽车,我是否需要一个编译时警告,该列表仅适用于带儿童座椅和黑猩猩的汽车?普通对象实例的列表开始看起来是个好主意。
通用代码可能包含许多单词和字符,并且会占用大量额外空间,并且需要花费更长的时间才能读取。我可以花很多时间使所有这些额外的代码正常工作。当这样做的时候,我会发疯。我也浪费了自己的几个小时或更长时间,我想知道是否还有其他人能够弄清楚代码。我希望能够将其交付给比自己不聪明或没有时间浪费的人进行维护。
另一方面(我在这里感到有些不适,觉得需要提供一些平衡),当使用简单的集合和映射在编写时进行添加,放置和获取检查时,这很好,并且通常不会增加太多复杂的代码(如果 有人 别人 写的集合或地图)。Java比C#更好。似乎我想使用的所有C#集合都没有处理过泛型。(我承认我的收藏品口味怪异。)
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
这实际上取决于在程序启动和到达相关代码行之间需要多少运行时间。如果用户报告了错误,并且需要几分钟(或者,在最坏的情况下,可能需要数小时甚至数天)才能重现,则编译时验证的外观会越来越好……