Java 泛型:将无界类型转换为有界类型

huangapple go评论65阅读模式
英文:

Java Generics : Convert unbounded types to bounded types

问题

以下是翻译后的内容:

我有两个定义如下的函数:

<T1 extends A> void func1(Class<T1> C);

<T2 extends B> void func2(Class<T2> C);

现在用户将在运行时提供任意类型,根据用户提供的类型,我们需要调用其中的func1或func2。我需要类似于以下的实现:

<T> void func(Class<T> C){
    // 检查类C是否扩展自A,如果是则调用func1,否则调用func2
    if (C extends A){
        func1(C);
    }
    else{
        func2(C);
    }
}

我刚开始接触Java泛型,我想知道是否有可能实现这个逻辑?如果可能的话,应该如何做到呢?

英文:

I have two functions defined as

&lt;T1 extends A&gt; void func1(Class&lt;T1&gt; C);

&lt;T2 extends B&gt; void func2(Class&lt;T2&gt; C);

Now user will give any type at runtime, based on what he gives we need to call either func1 or func2. I need something similar to

&lt;T&gt; void func(Class&lt;T&gt; C){
    // check if Class C extends A, then call func1 else call func2
    if (T extends A){
        func1(C);
    }
    else{
        func2(C);
    }
}

I am new to Java Generics and I want to know whether this is possible or not? And if possible how should I do it?

答案1

得分: 3

不是的,至少在泛型的情况下不是这样。

泛型几乎完全是编译器想象出来的东西。

在编译时,泛型信息是存在的,但仅限于肉眼所能看到的程度。如果我写下:

class Example<T> {
    void foo(T in) {}
}

那么仅仅通过看这段代码,你的肉眼无法确定在foo方法的任何调用中,T会是什么类型。因此编译器也无法确定,在编译时你也不知道T是什么。你不能编写这样的代码: "给我任何T的实例,也许以java.lang.Class实例的形式呈现"。

在运行时,这些信息已经不存在了——这就是擦除。

在运行时,有一些泛型信息是可用的——基本上,在签名中出现泛型的任何地方(即字段的类型,任何在extendsimplements语句中提到的类型,任何方法的返回类型,任何方法的参数类型)——你仍然可以在运行时获得这些信息,但你获得的只是你代码中直接写的内容。

因此,一旦你提出这样的问题:我是否可以将诸如jlClassInstance.isAssignableFrominstanceof这样的运行时概念与泛型边界结合起来?答案很简单,是一个坦率的:“不行。不可能。”,而这恰恰是你所在问的。

我确实提到过,只要所讨论的方法在其源代码中明确列出了类型,那么它现在就成为了签名的一部分,你理论上可以在运行时查询这些信息。因此,如果你有:

    <T extends Number> void func1(Class<T> c) { ... }
    <T extends List<?>> void func2(Class<T> c) { ... }

那么在运行时,经过一系列的花招和巧妙的代码,是可以检索到Number.classList.class的。但是,你不应该这样做,这是滥用泛型的行为。如果有人写了这样的代码:

    class Foo<Y> {
        <T extends Y> void func3(Class<T> c) { ... }
    }

因为这段代码可以编译通过,而你的计划是:我将使用反射来检查实际的类型边界,并在运行时使用它来分派一些操作——在这里是行不通的。你在运行时所能获得的信息只是名字是'T',其下界是'Y',而'Y'是封闭类型的无界类型参数。

英文:

It's not, at least, not with generics.

Generics are almost entirely a figment of the compiler's imagination.

At compile time, generics info is there, but only as much as your eyeballs can see. If I write:

class Example&lt;T&gt; {
    void foo(T in) {}
}

then your eyeballs cannot tell what T is going to be on any given invocation of the foo method, looking just at this code. So the compiler can't tell either, and at compile time you therefore don't know what the T is. You can't write code that goes: "Give me whatever T is, perhaps in the form of a java.lang.Class instance".

At runtime this information no longer exists - that's called erasure.

There is some generics available at runtime - basically, any time generics appears in a signature (that is, the type of a field, of any type mentioned in an extends or implements line, any method return type, any method parameter type) - you can still get that at runtime, but all you get is what is literally written in your code.

So, once you ask the question: Can I somehow mix runtime concepts such as jlClassInstance.isAssignableFrom and instanceof with a generics bound, the answer is a simple, flat: No. Not possible. - and yet that's what you are asking.

I did mention that, PROVIDED that the method(s) in question have, in their source code, the types explicitly listed, well, now it's part of signatures and you can theoretically query this at runtime. So if you have:

    &lt;T extends Number&gt; void func1(Class&lt;T&gt; c) { ... }
    &lt;T extends List&lt;?&gt;&gt; void func2(Class&lt;T&gt; c) { ... }

then it IS possible to, at runtime, retrieve, after a ton of hoops and tricky code, Number.class and List.class. But, you shouldn't do this, this is generics abuse. What if someone wrote:

    class Foo&lt;Y&gt; {
        &lt;T extends Y&gt; void func3(Class&lt;T&gt; c) { ... }
    }

Because that would compile fine, and your plan of: I will use reflection to check the actual type bound and use that to at runtime dispatch some stuff - won't work here. All you'll ever be able to tell at runtime here is that the name is 'T', its lower bound is 'Y', and 'Y' is a boundless type param of the enclosing type.

答案2

得分: 1

考虑这个问题:为什么 Java 中提供了泛型?

泛型被提供用于在编译时而不是运行时检测更多的类型转换错误。

现在让我们回到你的问题:

现在用户将在运行时提供任何类型,基于他提供的类型,我们需要调用 func1 或 func2。

这与 Java 中提供的泛型的设计初衷正好相悖,因为你希望在运行时传递任何类型。我建议你改变设计方式,使用户不能在运行时传递任意类型给你的方法,这种设计容易出错,并且这些错误将会在运行时被检测到!

英文:

Consider this question: Why Generics are provided in Java?

Generics are provided to detect more type cast error at compile time rather than runtime.

Now let's back to your question:

> Now user will give any type at runtime, based on what he gives we need
> to call either func1 or func2.

It is exactly against something that Generics are provided for in Java because you want to give any type at runtime, I suggest you change your design in a way that user can not pass any type at runtime to your method, this design is error prone and those errors will be detected at runtime!

huangapple
  • 本文由 发表于 2020年9月1日 19:45:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/63687021.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定