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

发布于 2024-08-15 03:06:36 字数 921 浏览 4 评论 0原文

这是我最近在一次采访中提出的一个问题,因为候选人希望看到 Java 语言中添加一些内容。 Java 没有具体化的泛型,这通常被认为是一种痛苦 但是,当我催促时,候选人实际上无法告诉我,如果他们在那里,他可以取得什么样的成就。

显然,由于 Java 中允许原始类型(以及不安全检查),因此有可能破坏泛型并最终得到一个(例如)实际上包含 StringList>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?

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 generics but, when pushed, the candidate couldn't actually tell me the sort of things that he could have achieved were they there.

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!

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 List at runtime - but what would you do with it?

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 Class as 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:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

倾城花音 2024-08-22 03:06:36

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

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

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) { ... }
a√萤火虫的光℡ 2024-08-22 03:06:36

从我几次遇到这个“需求”来看,它最终归结为这个构造:

public class Foo<T> {

    private T t;

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

}

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

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

public class Foo<T> {

    private T t;

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

}

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

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

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 T has a default constructor. You can even get the runtime type by typeof(T) and get the constructors by Type.GetConstructor().

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

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-catch is omitted for brevity)

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:

海螺姑娘 2024-08-22 03:06:36

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

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

Type safety comes 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 Class along as well. That way, your external interface depends on your implementation (whether you use RTTI about your parameters or not).

拍不死你 2024-08-22 03:06:36

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

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

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

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
扮仙女 2024-08-22 03:06:36

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

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

与 Java 泛型相关的大问题:

  • 这种可怕的装箱要求以及基元和引用类型之间的脱节。这与具体化或类型擦除没有直接关系。 C#/Scala 修复了这个问题。
  • 没有自我类型。为此,JavaFX 8 必须删除“构建器”。与类型擦除完全无关。 Scala 修复了这个问题,不确定 C# 是否如此。
  • 无声明方类型差异。 C# 4.0/Scala 有这个。与类型擦除完全无关。
  • 无法重载 void method(List l)method(List l)。这是由于类型擦除造成的,但非常小。
  • 不支持运行时类型反射。这是类型擦除的核心。如果您喜欢在编译时验证和证明尽可能多的程序逻辑的超级高级编译器,那么您应该尽可能少地使用反射,并且这种类型的类型擦除不会打扰您。如果您喜欢更多的补丁、脚本、动态类型编程,并且不太关心编译器尽可能多地证明您的逻辑正确,那么您想要更好的反射,并且修复类型擦除很重要。

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.

The big problem related to Java Generics:

  • 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.
烈酒灼喉 2024-08-22 03:06:36

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

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

我们要做的是

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

看起来丑陋但工作起来很时髦。

也有一些情况,说这样的话确实很有帮助:

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

这些事情并不经常咬人,但当它们发生时,它们确实会咬人。

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.

小矜持 2024-08-22 03:06:36

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

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

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

class ParametericException<T> extends Exception // compile error

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

如果上面的代码有效,那么可以通过以下方式进行异常处理:

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

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

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.

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 catch clause 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.

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.

微暖i 2024-08-22 03:06:36

如果数组具体化的话,它们可能会与泛型一起更好地发挥作用。

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

廻憶裏菂餘溫 2024-08-22 03:06:36

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

该 API 类似于 Iterator,其中 T 是只能使用构造函数中的字符串来构造的某种类型。然后迭代器查看从 sql 查询返回的字符串,然后尝试将其与类型 T 的构造函数匹配。

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

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

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

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.

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.

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.

靑春怀旧 2024-08-22 03:06:36

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

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

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.

瑾兮 2024-08-22 03:06:36

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

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

It's not that you will achieve anything extraordinary. It will just be simpler to understand. Type erasure seems like a hard time for beginners, and it ultimately requires one's understanding on the way the compiler works.

My opinion is, that generics are simply an extra that saves a lot of redundant casting.

荒岛晴空 2024-08-22 03:06:36

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

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

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

Something that all the answers here have missed that is constantly a headache for me is since the types are erased, you cannot inherit a generic interface twice. This can be a problem when you want to make fine grained interfaces.

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

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!
新一帅帅 2024-08-22 03:06:36

这是我今天遇到的一个问题:在没有具体化的情况下,如果您编写一个接受通用项目的可变参数列表的方法……调用者可能会认为它们是类型安全的,但意外地传入了任何旧的crud,并炸毁了您的方法。

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

(注意:我可能在这里犯了一个错误,但是谷歌搜索“generics varargs”,上面的内容似乎正是您所期望的。我认为,使这个问题成为实际问题的是 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
    }

上面的方法必须在一个单独的项目中编写数千次 - 所以可能性。因为人为错误变得很高。事实证明,我正在寻找替代方案,但不要抱太大希望。

Here's one that's caught me today: without reification, if you write a method that accepts a varargs list of generic items ... callers can THINK they're typesafe, but accidentally pass in any-old crud, and blow up your method.

Seems unlikely that would happen? ... Sure, until ... you use Class as your datatype. At this point, your caller will happily send you lots of Class objects, but a simple typo will send you Class objects that don't adhere to T, and disaster strikes.

(NB: I may have made a mistake here, but googling around "generics varargs", the above appears to be just what you'd expect. The thing that makes this a practical problem is the use of Class, I think - callers seem to be less careful :( )


For instance, I'm using a paradigm that uses Class objects as a key in maps (it's more complex than a simple map - but conceptually that's what's going on).

e.g. this works great in Java Generics (trivial example) :

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. without reification in Java Generics, this one accepts ANY "Class" object. And it's only a tiny extension of the previous code :

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

The above methods have to be written out thousands of times in an individual project - so the possibility for human error becomes high. Debugging mistakes is proving "not fun". I'm currently trying to find an alternative, but don't hold much hope.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文