我为什么要关心Java没有具体化的泛型?

时间:2009-12-18 11:54:02

标签: java generics reification

这是我最近在采访中提出的一个问题,即候选人希望看到添加到Java语言中的问题。它通常被认为是Java没有reified generics的痛苦,但是,当被推动时,候选人实际上无法告诉我他在那里可能取得的那些事情。

显然因为原始类型在Java中是允许的(以及不安全的检查),所以可以颠覆泛型并最终得到List<Integer>(例如)实际包含String s。如果类型信息具体化,这显然是不可能的; 但必须超过此

人们可以发布他们真正想要做的事情的例子,是否有可用的具体化的泛型?我的意思是,显然你可以在运行时获得List的类型 - 但是你会用它做什么?

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?

13 个答案:

答案 0 :(得分:99)

最常引诱我的是无法利用多个泛型类型的多个调度。以下是不可能的,并且在许多情况下它将是最佳解决方案:

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }

答案 1 :(得分:81)

从我遇到这个“需要”的几次,它最终归结为这个结构:

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

这在C#中有效,假设T具有默认构造函数。您甚至可以按typeof(T)获取运行时类型,并通过Type.GetConstructor()获取构造函数。

常见的Java解决方案是将Class<T>作为参数传递。

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(它不一定需要作为构造函数参数传递,因为方法参数也很好,上面只是一个例子,为了简洁,省略了try-catch

对于所有其他泛型类型构造,可以通过反射帮助轻松解析实际类型。以下Q&amp; A说明了用例和可能性:

答案 2 :(得分:34)

想到

类型安全。如果没有具体的泛型,向下转换为参数化类型将始终是不安全的:

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!? 

此外,抽象会泄漏 - 至少是可能对其类型参数的运行时信息感兴趣的那些。今天,如果您需要任何类型的通用参数的运行信息,您还必须传递其Class。这样,您的外部接口取决于您的实现(无论您是否使用RTTI关于您的参数)。

答案 3 :(得分:26)

您可以在代码中创建通用数组。

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}

答案 4 :(得分:16)

这是一个老问题,有很多答案,但我认为现有的答案是不合适的。

&#34;物化&#34;只是意味着真实,通常只是意味着与类型擦除相反。

与Java Generics相关的一个大问题:

  • 这种可怕的拳击要求和基元与参考类型之间的脱节。这与具体化或类型擦除没有直接关系。 C#/ Scala解决了这个问题。
  • 没有自我类型。 JavaFX 8必须删除&#34;构建者&#34;为此原因。绝对与类型擦除无关。 Scala解决了这个问题,对C#不确定。
  • 没有声明方类型差异。 C#4.0 / Scala有这个。绝对与类型擦除无关。
  • 不能超载void method(List<A> l)method(List<B> l)。这是由于类型擦除,但非常小。
  • 不支持运行时类型反射。这是类型擦除的核心。如果你喜欢在编译时验证和证明你的程序逻辑的超级高级编译器,你应该尽可能少地使用反射,这种类型的擦除不应该​​打扰你。如果你喜欢更多不完整,脚本化,动态类型的编程,并且不太关心编译器尽可能多地证明你的逻辑正确,那么你想要更好的反射和修复类型擦除很重要。

答案 5 :(得分:12)

通过具体化,序列化将更加直接。我们想要的是

deserialize(thingy, List<Integer>.class);

我们要做的是

deserialize(thing, new TypeReference<List<Integer>>(){});

看起来丑陋而且很有趣。

在某些情况下,说出像

这样的内容会非常有帮助
public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

这些东西经常不咬人,但是当它们发生时它们会咬人。

答案 6 :(得分:11)

我对Java Geneircs的曝光非常有限,除了其他答案已经提到的观点之外,Maurice Naftalin和Philip Walder在书中解释了 Java Generics and Collections 中的一个场景,具体化的泛型有用。

由于类型不可恢复,因此无法使用参数化异常。

例如,以下表格的声明无效。

class ParametericException<T> extends Exception // compile error

这是因为 catch 子句检查抛出的异常是否与给定类型匹配。此检查与实例测试执行的检查相同,并且由于该类型不可恢复,因此上述形式的语句无效。

如果上述代码有效,则可以采用以下方式进行异常处理:

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

本书还提到,如果定义Java泛型与C ++模板的方式类似 定义(扩展)它可以导致更有效的实施,因为这提供了更多 优化的机会。但是除此之外不提供任何解释,因此知识渊博的人的任何解释(指针)都会有所帮助。

答案 7 :(得分:8)

如果它们被神化,阵列可能会更好地使用泛型。

答案 8 :(得分:5)

我有一个包装器,它将jdbc结果集显示为迭代器,(这意味着我可以通过依赖注入更轻松地对数据库生成的操作进行单元测试)。

API看起来像Iterator<T>,其中T是某种类型,只能在构造函数中使用字符串构造。然后,Iterator查看从sql查询返回的字符串,然后尝试将其与T类型的构造函数匹配。

在当前实现泛型的方式中,我还必须传入我将从结果集创建的对象的类。如果我理解正确,如果泛型被统一,我可以调用T.getClass()获取它的构造函数,然后不必转换Class.newInstance()的结果,这将更加整洁。

基本上,我认为这使得编写API(而不仅仅是编写应用程序)变得更容易,因为你可以从对象中推断出更多,因此需要更少的配置......我没有意识到它的含义注释直到我看到它们被用在诸如spring或xstream之类的东西中而不是配置的大量内容。

答案 9 :(得分:5)

一件好事就是避免原始(值)类型的装箱。这与其他人提出的阵列投诉有些相关,如果内存使用受到限制,实际上可能会产生显着差异。

在编写框架时,还有几种类型的问题,其中能够反映参数化类型很重要。当然,这可以通过在运行时传递类对象来解决,但这会掩盖API并给框架的用户带来额外的负担。

答案 10 :(得分:2)

并不是说你会取得非凡的成就。理解起来会更简单。对于初学者来说,类型擦除似乎很难,最终需要对编译器的工作方式有所了解。

我的观点是,泛型只是额外的,可以节省大量的冗余。

答案 11 :(得分:0)

这是今天抓住我的一个:没有具体化,如果你写一个方法接受一个通用项目的varargs列表......来电者可以认为他们是类型安全的,但不小心传入任何旧的crud,并炸毁你的方法

似乎不太可能发生这种情况? ...当然,直到......你使用Class作为你的数据类型。此时,您的调用者将很乐意向您发送大量的Class对象,但是一个简单的拼写错误会向您发送不符合T的Class对象,以及灾难发生。

(注意:我可能在这里犯了一个错误,但在google上搜索“generics varargs”,上面看起来就像你期望的那样。这使得这个实际问题的事情就是使用Class,我想 - 来电者似乎不那么小心:()


例如,我正在使用一个范例,它使用Class对象作为地图中的一个关键字(它比一个简单的地图更复杂 - 但从概念上来说,这就是正在发生的事情)。

e.g。这在Java Generics中非常有用(简单的例子):

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

e.g。如果没有Java泛型中的具体化,这个接受任何“类”对象。它只是前一代码的一小部分:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

上述方法必须在单个项目中写出数千次 - 因此人为错误的可能性变高。调试错误证明“不好玩”。我目前正试图寻找替代方案,但不抱太大希望。

答案 12 :(得分:0)

这里所有答案都遗漏的东西一直是我头疼的问题,因为类型被删除,你不能继承两次通用接口。当您想要制作细粒度的接口时,这可能是一个问题。

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!