T extends U 的用途是什么?

18

最近我遇到了一个类似于这样定义的方法,但我并不完全理解它的用法:

public static <T, U extends T> T foo(U u) { ... }

一个示例用法可以是这样的:
// Baz is just the containing class of foo()
Number n = Baz.foo(1);

T 被推断为 NumberU(很可能)被推断为 Integer 时,这种方法优于例如这个方法定义:

public static <T> T bar(T t) { ... }

如果我这样调用它:
Number n = Baz.bar(2);

代码仍然有效。`T` 推断为 `Number` 或 `Integer`(不确定参数类型,例如此示例中的 `Integer` 是否优于调用站点返回类型 `Number`)。
我已阅读以下问题:12,但我仍然不知道具有两个参数的第一个方法是否比仅使用一个泛型的第二个方法具有任何优势。

8
也许你简化的例子略过了一个额外的T有意义的方面。你能否提供真实签名的链接/内容? - Thilo
@Thilo 我没有真正的代码示例。我在看到这个问题时遇到了它。在那里的使用似乎是一个Bean解析机制,你可以定义一个委托。比如这样 AutoBean<Object> autoBean = Foo.<Object, String>getAutoBean("delegate")。但是仍然不太有意义,因为单一泛型参数的结果是相同的。传递一个 Object 并收到一个 Object - Lino
1
那是不同的。它返回的不是T,而是Something<T> - RealSkeptic
@RealSkeptic 哎呀!你是对的,在那种情况下它是有意义的(我想)。因为泛型不是隐式多态的(我想这就是它被称为的方式)。String 扩展了 Object 但是 Something<String> 不扩展 Something<Object> - Lino
2
@Lino没错,但我还是不太确定。 我认为你的<T,U extends T> T foo(U u)中的“特定”并没有什么不同,只需删除U并将其替换为T。 Marco13提供的示例与您的示例不完全相同:*它使用的是List <U>而不是简单的U*。 我同意,在parameterized type*( Something <U>)的上下文中,它会有意义,但我怀疑在type parameter*(U)的上下文中是否也是如此。 - MC Emperor
显示剩余5条评论
4个回答

12

我认为,实际上只有当方法的类型参数出现在方法签名中作为参数化类型的类型参数时,这才有意义。

(至少我无法快速想出其他真正有意义的例子)

这也是你链接到的问题中的情况,在那里方法类型参数被用作AutoBean类的类型参数。


一个小更新:

根据问题和其他答案中的讨论,这个问题的核心可能是对类型参数使用方式的误解。因此,这个问题可以被视为Java函数声明中<T, U extends T>的含义的重复,但希望这个答案仍然有所帮助。

最终,使用<T,U extends T>模式的原因可以看作是参数化类型的继承关系,这在细节上可能会变得相当复杂。例如,为了说明最相关的点:一个List<Integer>不是List<Number>的子类型。


下面是一个示例,展示了它可以产生影响的情况。它包含一个“微不足道”的实现,它总是有效的(据我所知没有意义)。但当类型参数TU也是方法参数和返回类型的类型参数时,类型约束变得重要。使用T extends U,你可以返回一个具有超类型作为类型参数的类型。否则,如// Does not work所示,你不能。

import java.util.ArrayList;
import java.util.List;

public class SupertypeMethod {
    public static void main(String[] args) {

        Integer integer = null;
        Number number = null;

        List<Number> numberList = null;
        List<Integer> integerList = null;

        // Always works:
        integer = fooTrivial(integer);
        number = fooTrivial(number);
        number = fooTrivial(integer);

        numberList = withList(numberList);
        //numberList = withList(integerList); // Does not work

        // Both work:
        numberList = withListAndBound(numberList);
        numberList = withListAndBound(integerList);
    }

    public static <T, U extends T> T fooTrivial(U u) {
        return u;
    }

    public static <T, U extends T> List<T> withListAndBound(List<U> u) {
        List<T> result = new ArrayList<T>();
        result.add(u.get(0));
        return result;
    }

    public static <T> List<T> withList(List<T> u) {
        List<T> result = new ArrayList<T>();
        result.add(u.get(0));
        return result;
    }

}

(当然,这看起来有点牵强,但我认为人们可以想象出实际情况下这是有意义的)

你是对的。在这种情况下,这样做有点合理。不过,也可以只写public static <T> List<T> withList(List<? extends T> l),这样就可以适用于numberList = withList(integerList)。也许这只是Java语言中的一些冗余。 - Lino
在这种情况下,这是有意义的。但在 OP 的示例中,接收到的参数是类型 U 而不是类型 Something<U> 的情况下,我无法想象实际上会有什么区别。 - MC Emperor
@MCEmperor,实际上我有点误解了我看到的代码(https://dev59.com/h2_Xa4cB1Zd3GeqP5dSZ)。在那个问题中,他们返回一个参数化类“AutoBean<T>”,而不是简单的“T”。所以我想这是有道理的。 - Lino
5
@Lino确实,与其使用U extends T,在方法签名中通常可以(并且在此示例中也可以)使用? extends T。无法使用? extends T的情况是,例如,在List<T> copyAndAdd(List<U> us, U anotherU)中,必须知道U以用于第二个参数。 - Marco13

4

当您想要返回超类型时,这非常方便;就像您在示例中展示的那样。

您以U为输入并返回T - 这是U的超类型; 声明这种关系的另一种方式是T super U,但在Java中不合法。

这应该是我实际意思的一个例子。假设一个非常简单的类,比如:

static class Holder<T> {

    private final T t;

    public Holder(T t) {
        this.t = t;
    }

    public <U super T> U whenNull(U whenNull){
        return t == null ? whenNull : t;
    }
}

该定义的方法whenNull无法编译,因为在Java中不允许使用U super T.

相反,您可以添加另一个类型参数并倒置类型:

static class Holder<U, T extends U> {

    private final T t;

    public Holder(T t) {
        this.t = t;
    }

    public U whenNull(U whenNull) {
        return t == null ? whenNull : t;
    }
}

使用方法如下:

Holder<Number, Integer> n = new Holder<>(null);
Number num = n.whenNull(22D);

这允许返回一个超类型;但它看起来非常奇怪。我们已经在类声明中添加了另一种类型。
我们可以采用以下方法:
static class Holder<T> {

    private final T t;

    public Holder(T t) {
        this.t = t;
    }

    public static <U, T extends U> U whenNull(U whenNull, Holder<T> holder) {
        return holder.t == null ? whenNull : holder.t;
    }
}

甚至可以将这种方法改为静态方法。

对于现有的限制,您可以尝试执行以下操作:

Optional.ofNullable(<SomeSubTypeThatIsNull>)
        .orElse(<SomeSuperType>)

8
你其实不需要这样做,因为U本来就可以赋值给T。 - RealSkeptic
我同意@RealSkeptic的观点。在这种情况下,U仍然可以分配给T,因此T就足够了,因为您可以将任何子类型传递给该方法。 - Lino
我认为他是对的,会相应地更新我的答案。 - GhostCat

2

第一种方法

public static <T, U extends T> T foo(U u) { ... }

这意味着TU可以是不同的类型。即一个类型T和一个类型U,它是T的子类型。

以您的第二个示例为例:

public static <T> T bar(T t) { ... }

bar(T t) 必须返回与参数t相同的类型。它不能返回一个超类类型的对象,这只有在您的第一种变体中才可能。


1
但它返回的对象可以被分配或在任何需要其超类型的上下文中使用。 - RealSkeptic
这并不一定是真的。该方法实际上可能返回一个不是具体类型U,而是另一个具体类型X的对象,该对象也是T的子类。在某些情况下,这可能是有用的。 - Thomas Kainrad
@ThomasKainrad,虽然你不知道T到底是什么。因为整个上下文都是泛型的。你不能确定X是否真的是T的子类型。 - Lino
我可以确定该方法返回一个超类型为 T 的对象,因为这在其签名中。然后,我可以通过 instanceof 检查它是哪种具体类型。它可能是 X,并且肯定是 T 的子类型。 - Thomas Kainrad
@ThomasKainrad 使用instanceof实际上违背了泛型设计初衷。但是我同意,这种用法也是可以的。 - Lino
2
从我的角度来看,这个论点并不是关于这是否有意义或者是否是好的设计。我只是想指出,在某些情况下,第一种选择提供了第二种选择所没有的功能。 - Thomas Kainrad

2
最初的回答是:我的第一反应是:哎呀,
Number n = Baz.bar(2);

"始终"有效,因为Integer扩展了Number。所以这样做没有任何优势。但是如果您有一个不是抽象的超类呢?!然后,U extends T允许您返回仅为超类型类的对象,而不是子类!例如:
class B { } 
class C extends B { }

现在通用的方法也可以返回B的实例。如果只有T,那么该方法只能返回C的实例。
换句话说:U extends T允许您返回B和C的实例。T单独时:只有C!
但当一个方法(实际上)仅返回B的实例时,为什么首先需要使用泛型呢?
所以,我同意这个问题:我也看不到这种构造的实际价值。除非涉及到反射,但即使如此,我也看不到一个合理的设计,只能因为U extends T而工作。

1
@RealSkeptic 但是“仅限于C”并不等同于“B和C”。上述方法可能会返回“只有B”。这根本不像C! - GhostCat
1
不,@Eugene。这与问题是不同的,它返回Something<T>而不是T,这意味着它与Something<U>不是多态的,而T U多态的。请看一下对OP的评论。 - RealSkeptic
1
@RealSkeptic 我的意思是 Optional::orElse,我的错,它确实返回一个 T - Eugene
1
@RealSkeptic @Lino 看看这个例子:`static class Holder<T> { private final T t; public Holder(T t) { this.t = null; } // 这样的方法无法编译// public <U super T> whenNull(U defaultValue){ // return t == null ? defaultValue : t; // } } 正如注释所说 - 这是不可能的。你需要添加另一个类型才能够实现,通过:static class Holder<T, U extends T> {... }`,但这会增加另一个类型参数...(续在下一条评论中) - Eugene
1
对于像 List<T>Optional<T> 这样的示例,这绝不是一个好主意,只是为了能够返回超类型。唯一的解决方案是添加一个静态方法,其中包含您上面的定义,以便能够实现该目标。 - Eugene
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接