为什么我应该关心Java没有具体化的generics?
这个问题是我最近在一次采访中问的一个问题,那就是候选人希望添加到Java语言中。 Java通常被认为是一种痛苦,没有具体化的generics,但是当被推送时候,候选人实际上并不能告诉我他能在那里获得什么。
很显然,因为原始types在Java中是允许的(和不安全的检查),所以可以颠覆generics,最终得到一个List<Integer>
(例如)实际上包含了String
。 types信息具体化,这显然是不可能的; 但一定还有比这更多的 !
人们可以列举他们真正想要做的事情的例子吗? 我的意思是,显然你可以在运行时得到一个List
的types – 但是你会用它做什么?
public <T> void foo(List<T> l) { if (l.getGenericType() == Integer.class) { //yeah baby! err, what now?
编辑 :一个快速更新,因为答案似乎主要是关心需要传递一个Class
作为参数(例如EnumSet.noneOf(TimeUnit.class)
)。 我正在寻找更多的东西沿线,这是不可能的 。 例如:
List<?> l1 = api.gimmeAList(); List<?> l2 = api.gimmeAnotherList(); if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) { l1.addAll(l2); //why on earth would I be doing this anyway?
从我碰到这个“需要”的几次,最终归结为这个构造:
public class Foo<T> { private T t; public Foo() { this.t = new T(); // Help? } }
这在C#中工作,假设T
有一个默认的构造函数。 您甚至可以通过typeof(T)
获取运行时types,并通过Type.GetConstructor()
获取构造函数。
通用的Java解决scheme将是传递Class<T>
作为参数。
public class Foo<T> { private T t; public Foo(Class<T> cls) throws Exception { this.t = cls.newInstance(); } }
(它不一定需要作为构造函数parameter passing,因为方法参数也很好,以上只是一个例子,为简洁起见也省略了try-catch
)
对于所有其他genericstypes的构造,实际的types可以通过reflection来帮助解决。 下面的问答说明了用例和可能性:
- 获取java.util.List的genericstypes
- 如何在运行时获得genericstypes?
- 获取抽象超类的genericstypes参数的实际types
最常见的事情就是无法利用跨多个generics的多个分派。 以下是不可能的,有很多情况下,这将是最好的解决scheme:
public void my_method(List<String> input) { ... } public void my_method(List<Integer> input) { ... }
types安全性浮现在脑海中。 如果没有泛化的generics,向下转换为参数化types将始终是不安全的:
List<String> myFriends = new ArrayList(); myFriends.add("Alice"); getSession().put("friends", myFriends); // later, elsewhere List<Friend> myFriends = (List<Friend>) getSession().get("friends"); myFriends.add(new Friend("Bob")); // works like a charm! // and so... List<String> myFriends = (List<String>) getSession().get("friends"); for (String friend : myFriends) print(friend); // ClassCastException, wtf!?
此外, 抽象将泄漏较less – 至less那些可能对运行时信息感兴趣的types参数。 今天,如果你需要任何types的运行时信息,你必须传递它的Class
。 这样,你的外部接口取决于你的实现(无论你是否使用RTTI参数)。
你将能够在你的代码中创build通用数组。
public <T> static void DoStuff() { T[] myArray = new T[42]; // No can do }
这是一个古老的问题,有很多答案,但我认为现有的答案是不正确的。
“物化”仅仅意味着真实,通常意味着与删除types相反。
Javagenerics相关的大问题:
- 这个可怕的拳击要求和基元和参考types之间的断开。 这与物化或types擦除没有直接关系。 C#/ Scala解决了这个问题。
- 没有自我types。 由于这个原因JavaFX 8必须删除“build设者”。 与types擦除绝对没有关系。 斯卡拉修复了这一点,不知道关于C#。
- 没有声明方面的差异。 C#4.0 / Scala有这个。 与types擦除绝对没有关系。
- 不能重载
void method(List<A> l)
和method(List<B> l)
。 这是由于types擦除,但是非常小。 - 不支持运行时typesreflection。 这是删除types的核心。 如果你喜欢超级先进的编译器,在编译时validation和validation你的程序逻辑,你应该使用尽可能less的reflection,这种types的擦除不应该打扰你。 如果你喜欢更多的细节,脚本,dynamictypes的编程,并不关心一个编译器尽可能多的certificate你的逻辑是正确的,那么你想要更好的reflection和修复types的擦除是非常重要的。
我对Java Geneircs的接触是非常有限的,除了其他答案已经提到的要点之外,还有Maurice Naftalin和Philip Walder所着的Java Generics and Collections一书中的一个场景,其中泛化generics是有用的。
由于types不可确定,所以不可能有参数化exception。
例如下面的表单声明是无效的。
class ParametericException<T> extends Exception // compile error
这是因为catch子句检查抛出的exception是否匹配给定的types。 此检查与实例检查执行的检查相同,由于types不可确定,因此上述语句forms无效。
如果上面的代码是有效的,那么以下方式的exception处理是可能的:
try { throw new ParametericException<Integer>(42); } catch (ParametericException<Integer> e) { // compile error ... }
本书还提到,如果Javagenerics的定义类似于C ++模板的定义(扩展),可能会导致更高效的实现,因为这提供了更多的优化机会。 但是除此之外没有提供任何解释,所以来自知识渊博的人的任何解释(指针)都是有帮助的。
序列化将更直接与具体化。 我们想要的是
deserialize(thingy, List<Integer>.class);
我们要做的是
deserialize(thing, new TypeReference<List<Integer>>(){});
看起来丑陋,作品放松。
也有一些情况下说这样的话真的很有帮助
public <T> void doThings(List<T> thingy) { if (T instanceof Q) doCrazyness(); }
这些东西不经常咬,但它们发生时会咬人。
如果通用化,arrays可能会在仿制药上扮演更好的angular色。
我有一个将jdbc结果集作为迭代器的包装器(这意味着我可以通过dependency injection更容易地unit testing数据库源操作)。
API看起来像Iterator<T>
,其中T是一些可以在构造函数中只用string构造的types。 Iterator然后查看从sql查询返回的string,然后尝试将其与Ttypes的构造函数进行匹配。
在目前的generics实现方式中,我还必须传入将从结果集创build的对象的类。 如果我理解正确,如果generics被通用化了,我可以直接调用T.getClass()来获得它的构造函数,然后不需要抛出Class.newInstance()的结果,这将会更加简洁。
基本上,我认为它使得编写API(而不仅仅是编写应用程序)变得更容易,因为你可以从对象中推断出更多的东西,因此更less的configuration将是必要的…我不赞赏注释的含义,直到我看到他们正在使用像spring或xstream的东西,而不是大量的configuration。
一件好事就是避免原始(值)types的装箱。 这与其他人提出的arrays投诉有些相关,而在记忆力受限的情况下,实际上可能会产生显着的差异。
编写一个能够反映参数化types的框架也是很重要的。 当然,这可以通过在运行时传递一个类对象来解决,但是这会掩盖API并给框架的用户带来额外的负担。
这并不是说你会取得非凡的成就。 这将是更容易理解。 对于初学者来说,types擦除似乎是一个艰难的时刻,最终需要人们理解编译器的工作方式。
我的意见是,generics只是一个额外的节省了大量的冗余铸造。
下面是今天引起我注意的一个:如果你写了一个方法来接受通用项目的可变参数列表,呼叫者可以认为他们是types安全的,但是不小心传入了任何旧的垃圾邮件,并且炸毁了你的方法。
似乎不太可能会发生? …当然,直到…你使用Class作为你的数据types。 在这一点上,你的调用者会很高兴地向你发送大量的Class对象,但是一个简单的错字会向你发送不符合T的Class对象,并且发生灾难。
(注:我可能在这里犯了一个错误,但是在“generics可变参数”上search,上面的内容似乎正是你所期望的。这个问题使得这个实际问题是使用Class,我认为 – 调用者似乎要小心点:()
例如,我使用的是一个使用Class对象作为地图中的关键的范例(它比简单的地图更复杂 – 但概念上就是这样)。
例如,这在Javagenerics(微不足道的例子)中效果很好:
public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType) { // find the entities that are mapped (somehow) from that class. Very type-safe }
例如,没有在Javagenerics的泛化,这个接受任何“类”对象。 这只是以前的代码的一个小小的扩展:
public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType ) { // find the entities that are mapped (somehow) to ALL of those classes }
上述方法必须在单个项目中被写出数千次 – 所以人为错误的可能性变得很高。 debugging错误被certificate是“不好玩”。 我目前正在试图find一个替代scheme,但没有太多的希望。
这里所有的答案都遗漏了,这对我来说一直是头疼的事情,因为types被擦除了,所以你不能inheritance两次通用接口。 这可能是一个问题,当你想制作细粒度的接口。
public interface Service<KEY,VALUE> { VALUE get(KEY key); } public class PersonService implements Service<Long, Person>, Service<String, Person> //Can not do!!