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

oxb*_*kes 101 java generics reification

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

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

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

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

编辑:对此的快速更新,因为答案似乎主要是关注需要传入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?
Run Code Online (Sandbox Code Playgroud)

RHS*_*ger 99

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

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
Run Code Online (Sandbox Code Playgroud)

  • @Tom:由于类型擦除,这甚至都没有编译.两者都编译为`public void my_method(List input){}`.然而,我从来没有遇到过这种需要,仅仅是因为它们没有相同的名称.如果它们具有相同的名称,我会质疑`public <T extends Object> void my_method(List <T> input){}`是不是更好的主意. (26认同)
  • 绝对没有必要能够做到这一点.当编译时类型信息可用时,在编译时完成方法选择. (5认同)
  • @Fabian:这意味着你必须拥有单独的代码,并防止你从C++中的`<algorithm>`获得的优势. (4认同)

Bal*_*usC 81

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

public class Foo<T> {

    private T t;

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

}
Run Code Online (Sandbox Code Playgroud)

这在C#中有效,假设它T有一个默认构造函数.您甚至可以通过获取运行时类型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();
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 它在C#中编译,前提是您将类型声明为:public class Foo <T>其中T:new().这会将有效类型的T限制为包含无参数构造函数的类型. (33认同)
  • 这在C#中起作用(例如)?你怎么知道T有一个默认的构造函数? (5认同)
  • 是的,确实如此.这只是一个基本的例子(假设我们使用javabeans).重点是,使用Java,你不能在**运行时**中通过`T.class`或`T.getClass()`获取类,这样你就可以访问它的所有字段,构造函数和方法.它使建筑也变得不可能. (4认同)
  • 这对我来说似乎真的很弱,因为它是"大问题",特别是因为这只能与一些围绕构造函数/参数的脆弱反射相结合. (3认同)
  • @Colin你实际上可以告诉java需要一个给定的泛型类型来扩展多个类或接口.请参阅http://docs.oracle.com/javase/tutorial/java/generics/bounded.html的"多个边界"部分.(当然,你不能告诉它该对象将是一个特定的集合之一,它不共享可以抽象到接口中的方法,这可以在C++中使用模板专门化来实现.) (3认同)

gus*_*afc 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!? 
Run Code Online (Sandbox Code Playgroud)

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


Tur*_*nor 26

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

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

  • 类型安全.我可以在Object []中添加我想要的任何内容,但只能在String []中添加字符串. (19认同)
  • Ran:没有讽刺:您可能喜欢使用脚本语言而不是Java,那么您可以在任何地方灵活地使用无类型变量! (3认同)
  • 两种语言中的数组都是协变的(因此不是类型安全的).`String [] strings = new String [1]; Object [] objects = strings; objects [0] = new Object();`在两种语言中编译都很好.运行notsofine. (2认同)

use*_*301 16

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

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

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

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

  • 我认为这是最好的答案.特别是描述由于类型擦除而基本上是什么问题以及Java语言设计问题的部分.我告诉人们开始擦除呜呜声的第一件事是Scala和Haskell也在通过类型擦除工作. (3认同)

Cog*_*man 12

具体化后,序列化将更加直接.我们想要的是

deserialize(thingy, List<Integer>.class);
Run Code Online (Sandbox Code Playgroud)

我们要做的是

deserialize(thing, new TypeReference<List<Integer>>(){});
Run Code Online (Sandbox Code Playgroud)

看起来很丑,很有趣.

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

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }
Run Code Online (Sandbox Code Playgroud)

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


sat*_*esh 11

我对Java Geneircs的接触是非常有限的,除了其他答案已经提到的要点之外,还有一个由Maurice Naftalin和Philip Walder 撰写的Java Generics and Collections一书中解释的场景,其中有效的泛型.

由于类型不可恢复,因此不可能具有参数化异常.

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

class ParametericException<T> extends Exception // compile error
Run Code Online (Sandbox Code Playgroud)

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

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

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}
Run Code Online (Sandbox Code Playgroud)

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


Han*_*Gay 8

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

  • 数组的问题在于它们的协方差,与具体化无关. (3认同)
  • 当然,但他们仍然会有问题.`List <String>`不是`List <Object>`. (2认同)

Jam*_*s B 5

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

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

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

基本上,我认为这使得编写API(而不仅仅是编写应用程序)更容易,因为你可以从对象中推断出更多,因此需要更少的配置......我不理解注释的含义,直到我看到它们被用于像spring或xstream这样的东西而不是配置的大量.


kvb*_*kvb 5

一个好处是避免原始(值)类型的装箱.这与其他人提出的阵列投诉有些相关,而在内存使用受到限制的情况下,它实际上可能会产生显着差异.

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

  • 这基本上归结为能够做'新T []`,其中T是原始类型! (2认同)