103

这是我最近在一次采访中提出的一个问题,作为候选人希望看到添加到 Java 语言中的东西。Java 没有具体化泛型通常被认为是一种痛苦,但是当被推动时,候选人实际上无法告诉我如果他们在那里他可以实现什么样的事情。

显然,因为 Java 中允许使用原始类型(以及不安全检查),所以有可能颠覆泛型并最终得到一个List<Integer>(例如)实际上包含Strings 的类型。如果类型信息被具体化,这显然是不可能的;但肯定不止这些

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

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

编辑:对此的快速更新,因为答案似乎主要关注需要传入 aClass作为参数(例如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?
4

13 回答 13

101

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

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
于 2009-12-18T12:35:06.347 回答
81

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

public class Foo<T> {

    private T t;

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

}

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

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

public class Foo<T> {

    private T t;

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

}

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

对于所有其他泛型类型构造,实际类型可以通过反射的一点帮助轻松解决。以下问答说明了用例和可能性:

于 2009-12-18T12:05:51.043 回答
35

类型安全浮现在脑海中。如果没有具体化的泛型,向下转换为参数化类型总是不安全的:

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)。

于 2009-12-18T12:33:19.563 回答
27

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

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
于 2009-12-18T14:39:33.603 回答
17

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

“具体化”只是意味着真实,通常意味着与类型擦除相反。

与 Java 泛型相关的大问题:

  • 这种可怕的装箱要求以及原语和引用类型之间的断开。这与具体化或类型擦除没有直接关系。C#/Scala 解决了这个问题。
  • 没有自我类型。由于这个原因,JavaFX 8 不得不删除“构建器”。与类型擦除完全无关。Scala 解决了这个问题,不确定 C#。
  • 没有声明方类型差异。C# 4.0/Scala 有这个。与类型擦除完全无关。
  • 不能超载void method(List<A> l)method(List<B> l)。这是由于类型擦除,但非常小。
  • 不支持运行时类型反射。这是类型擦除的核心。如果你喜欢在编译时验证和证明尽可能多的程序逻辑的超级高级编译器,你应该尽可能少地使用反射,这种类型的擦除不应该​​打扰你。如果你喜欢更零散的、脚本化的、动态的类型编程,并且不太关心编译器尽可能多地证明你的逻辑正确,那么你想要更好的反射和修复类型擦除很重要。
于 2014-03-26T16:55:42.267 回答
14

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

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

我们要做的是

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

看起来很丑,而且工作起来很时髦。

在某些情况下,说类似的话会很有帮助

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

这些事情不常咬人,但发生时会咬人。

于 2014-08-06T23:07:37.613 回答
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++ 模板的定义方式(扩展),它可能会导致更有效的实现,因为这提供了更多的优化机会。但是没有提供更多的解释,所以来自知识渊博的人的任何解释(指针)都会有所帮助。

于 2009-12-18T18:18:12.323 回答
8

如果它们被具体化,数组可能会更好地与泛型一起使用。

于 2009-12-18T12:00:06.433 回答
5

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

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

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

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

于 2009-12-18T12:09:25.637 回答
5

一件好事是避免对原始(值)类型进行装箱。这与其他人提出的数组投诉有些相关,并且在内存使用受到限制的情况下,它实际上可能会产生重大影响。

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

于 2009-12-18T20:51:52.713 回答
3

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

我的观点是,泛型只是一个额外的东西,可以节省大量多余的转换。

于 2009-12-18T12:02:33.467 回答
1

这里的所有答案都错过了让我一直头疼的事情,因为类型被删除了,你不能继承一个泛型接口两次。当您想要制作细粒度的界面时,这可能是一个问题。

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

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!
于 2017-08-19T06:26:35.417 回答
0

这是今天吸引我的一个问题:没有具体化,如果您编写一个接受泛型项的可变参数列表的方法......调用者可以认为它们是类型安全的,但不小心传入了任何旧的 crud,并炸毁了您的方法。

似乎不太可能发生这种情况?...当然,直到...您使用 Class 作为数据类型。此时,您的调用者会很高兴地向您发送许多 Class 对象,但一个简单的拼写错误会向您发送不符合 T 的 Class 对象,并且灾难来袭。

(注意:我可能在这里犯了一个错误,但是在“泛型可变参数”周围搜索,上面似乎正是您所期望的。使这成为一个实际问题的事情是使用 Class,我认为 - 调用者似乎不那么小心:()


例如,我正在使用一个范例,它使用 Class 对象作为映射中的键(它比简单的映射更复杂 - 但从概念上讲就是这样)。

例如,这在 Java 泛型(简单示例)中效果很好:

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

例如,在 Java 泛型中没有具体化,这个接受任何“类”对象。它只是前面代码的一个很小的扩展:

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

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

于 2011-10-16T14:34:38.523 回答