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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1927789/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 18:33:55  来源:igfitidea点击:

Why should I care that Java doesn't have reified generics?

javagenericsreification

提问by oxbow_lakes

This came up as a question I asked in an interview recently as something the candidate wished to see added to the Java language. It's commonly-identified as a pain that Java doesn't have reified genericsbut, when pushed, the candidate couldn't actually tell me the sort of things that he could have achieved were they there.

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

Obviously because raw types are allowable in Java (and unsafe checks), it is possible to subvert generics and end up with a List<Integer>that (for example) actually contains Strings. This clearly could be rendered impossible were type information reified; but there must be more than this!

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

Could people post examples of things that they would really want to do, were reified generics available? I mean, obviously you could get the type of a Listat runtime - but what would you do with it?

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

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

EDIT: A quick update to this as the answers seem mainly to be concerned about the need to pass in a Classas a parameter (for example EnumSet.noneOf(TimeUnit.class)). I was looking more for something along the lines of where this just isn't possible. For example:

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

采纳答案by BalusC

From the few times that I came across this "need", it ultimately boils down to this construct:

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

public class Foo<T> {

    private T t;

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

}

This does work in C# assuming that Thas a defaultconstructor. You can even get the runtime type by typeof(T)and get the constructors by Type.GetConstructor().

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

The common Java solution would be to pass the Class<T>as argument.

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

public class Foo<T> {

    private T t;

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

}

(it does not necessarily need to be passed as constructor argument, as a method argument is also fine, the above is just an example, also the try-catchis omitted for brevity)

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

For all other generic type constructs, the actual type can easily be resolved with a bit help of reflection. The below Q&A illustrate the use cases and possibilities:

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

回答by RHSeeger

The thing that most commonly bites me is the inability to take advantage of multiple dispatch across multiple generic types. The following isn't possible and there are many cases where it would be the best solution:

最让我烦恼的是无法利用跨多个泛型类型的多重分派。以下是不可能的,并且在很多情况下它是最佳解决方案:

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

回答by gustafc

Type safetycomes to mind. Downcasting to a parametrized type will always be unsafe without reified generics:

想到了类型安全。如果没有具体化的泛型,向下转换为参数化类型总是不安全的:

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

Also, abstractions would leak less- at least the ones which may be interested in runtime information about their type parameters. Today, if you need any kind of runtime information about the type of one of the generic parameters you have to pass its Classalong as well. That way, your external interface depends on your implementation (whether you use RTTI about your parameters or not).

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

回答by Turnor

You'd be able to create generic arrays in your code.

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

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

回答by user2684301

This is an old question, there are a ton of answers, but I think that the existing answers are off the mark.

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

"reified" just means real and usually just means the opposite of type erasure.

“reified”只是意味着真实,通常只是意味着类型擦除的反面。

The big problem related to Java Generics:

Java泛型相关的大问题:

  • This horrible boxing requirement and disconnect between primitives and reference types. This isn't directly related to reification or type erasure. C#/Scala fix this.
  • No self types. JavaFX 8 had to remove "builders" for this reason. Absolutely nothing to do with type erasure. Scala fixes this, not sure about C#.
  • No declaration side type variance. C# 4.0/Scala have this. Absolutely nothing to do with type erasure.
  • Can't overload void method(List<A> l)and method(List<B> l). This is due to type erasure but is extremely petty.
  • No support for runtime type reflection. This is the heart of type erasure. If you like super advanced compilers that verify and prove as much of your program logic at compile time, you should use reflection as little as possible and this type of type erasure shouldn't bother you. If you like more patchy, scripty, dynamic type programming and don't care so much about a compiler proving as much of your logic correct as possible, then you want better reflection and fixing type erasure is important.
  • 这种可怕的装箱要求以及原语和引用类型之间的脱节。这与具体化或类型擦除没有直接关系。C#/Scala 修复了这个问题。
  • 没有自我类型。由于这个原因,JavaFX 8 不得不删除“构建器”。与类型擦除完全无关。Scala 修复了这个问题,不确定 C#。
  • 无声明端类型差异。C# 4.0/Scala 有这个。与类型擦除完全无关。
  • 不能超载void method(List<A> l)method(List<B> l)。这是由于类型擦除,但非常小。
  • 不支持运行时类型反射。这是类型擦除的核心。如果你喜欢在编译时验证和证明尽可能多的程序逻辑的超高级编译器,你应该尽可能少地使用反射,这种类型的擦除不应该​​打扰你。如果您喜欢更零散的、脚本式的、动态类型编程,并且不太在意编译器证明尽可能多的逻辑正确,那么您需要更好的反射和修复类型擦除很重要。

回答by Cogman

Serialization would be more straightforward with reification. What we would want is

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

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

What we have to do is

我们要做的是

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

looks ugly and works funkily.

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

There are also cases where it would be really helpful to say something like

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

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

These things don't bite often, but they do bite when they happen.

这些东西不常咬人,但一旦发生就会咬人。

回答by sateesh

My exposure to Java Geneircs is quite limited, and apart from the points other answers have already mentioned there is a scenario explained in the book Java Generics and Collections, by Maurice Naftalin and Philip Walder, where the reified generics are useful.

我对 Java Geneircs 的了解非常有限,除了其他答案已经提到的要点之外,Maurice Naftalin 和 Philip Walder所著的Java 泛型和集合一书中解释了一个场景,其中具体化的泛型很有用。

Since the types are not reifiable, it is not possible to have Parameterized exceptions.

由于类型不可具体化,因此不可能有参数化异常。

For example the declaration of below form is not valid.

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

class ParametericException<T> extends Exception // compile error

This is because the catchclause checks whether the thrown exception matches a given type. This check is same as the check performed by instance test and since the type is not reifiable the above form of statement is invalid.

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

If the above code was valid then exception handling in the below manner would have been possible:

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

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

The book also mentions that if Java generics are defined similar to the way C++ templates are defined (expansion) it may lead to more efficient implementation as this offers more opportunities for optimization. But doesn't offer any explanation more than this, so any explanation (pointers) from the knowledgeable folks would be helpful.

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

回答by Hank Gay

Arrays would probably play much nicer with generics if they were reified.

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

回答by James B

I have a wrapper that presents a jdbc resultset as an iterator, (it means I can unit test database-originated operations a lot easier through dependency injection).

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

The API looks like Iterator<T>where T is some type that can be constructed using only strings in the constructor. The Iterator then looks at the strings being returned from the sql query and then tries to match it to a constructor of type T.

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

In the current way that generics are implemented, I have to also pass in the class of the objects that I will be creating from my resultset. If I understand correctly, if generics were reified, I could just call T.getClass() get its constructors, and then not have to cast the result of Class.newInstance(), which would be far neater.

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

Basically, I think it makes writing APIs (as opposed to just writing an application) easier, because you can infer a lot more from objects, and thereby less configuration will be necessary...I didn't appreciate the implications of annotations until I saw them being used in things like spring or xstream instead of reams of config.

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

回答by kvb

One nice thing would be avoiding boxing for primitive (value) types. This is somewhat related to the array complaint that others have raised, and in cases where memory use is constrained it could actually make a significant difference.

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

There are also several types of problems when writing a framework where being able to reflect over the parameterized type is important. Of course this can be worked around by passing a class object around at runtime, but this obscures the API and places an additional burden on the user of the framework.

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