Использование T расширяет U?

В последнее время я столкнулся с методом, подобным этому, и я не совсем понимаю его использование:

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

Пример использования может быть таким:

// Baz is just the containing class of foo()
Number n = Baz.foo(1);

Где T выводится в Number а U (вероятно) в Integer. Но я не могу обернуться, когда это лучше, чем, например, определение этого метода:

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

Если я назову это так:

Number n = Baz.bar(2);

Код все еще работает. T выводится либо Number или Integer (Не знаю, если аргумент типа, в данном примере Integer является более предпочтительным, чем вызов сайт возвращаемого типа Number)

Я прочитал эти вопросы: 1, 2, но я все еще не знаю, имеет ли первый метод с 2 параметрами какое-либо преимущество перед вторым методом только с одним универсальным.

Ответ 1

Я думаю, что на самом деле это имеет смысл, только когда параметр типа метода появляется как параметр типа параметризованного типа, который является частью сигнатуры метода.

(По крайней мере, я не мог быстро придумать пример, где это действительно имело бы смысл в противном случае)

Это также относится к вопросу, с которым вы AutoBean, когда параметры типа метода используются в качестве параметров типа в классе AutoBean.


Небольшое обновление:

Основываясь на обсуждении в вопросе и других ответах, суть этого вопроса, вероятно, была неверной интерпретацией способа использования параметров типа. Таким образом, этот вопрос можно рассматривать как дубликат значения <T, U расширяет T> в объявлении функции Java, но, надеюсь, кто-то сочтет этот ответ полезным, тем не менее.

В конце концов, причина использования шаблона <T, U extends T> может быть замечена в отношениях наследования параметризованных типов, что в деталях может быть довольно сложным. В качестве примера, чтобы проиллюстрировать наиболее актуальную точку: List<Integer> не является подтипом List<Number>.


Пример, показывающий, где это может изменить ситуацию, приведен ниже. Он содержит "тривиальную" реализацию, которая всегда работает (и, насколько я могу судить, не имеет смысла). Но граница типа становится релевантной, когда параметры типа T и U также являются параметрами типа параметров метода и возвращаемого типа. Пока 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;
    }

}

(Конечно, это выглядит немного надуманным, но я думаю, что можно представить сценарии, в которых это действительно имеет смысл)

Ответ 2

Это удобно, когда вы хотите вернуть супер тип; Точно так же, как вы показали в своем примере.

Вы берете 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 как он определен, не будет компилироваться, так как U super T не допускается в Java.

Вместо этого вы можете добавить другой параметр типа и инвертировать типы:

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

Ответ 3

Моя первая мысль была: черт,

Number n = Baz.bar(2);

будет работать всегда, так как Integer расширяет число. Так что в этом нет никакого преимущества. Но что, если у вас был суперкласс, который не был абстрактным?!

Затем U extends T позволяет вам возвращать объект только класса супертипа, но не дочернего класса!

Что-то вроде

class B { } 
class C extends B { }

теперь этот универсальный метод также может возвращать экземпляр B. Если есть только T..., метод может возвращать только экземпляры C.

Другими словами: U extends T позволяет вам возвращать экземпляры B и C. T один: только C!

Но, конечно, вышесказанное имеет смысл, когда вы смотрите на некоторые конкретные B и C. Но когда метод (в действительности) просто возвращает экземпляр B, зачем вообще нужны здесь дженерики?!

Итак, я согласен с вопросом: я также не вижу практической ценности этой конструкции. Пока кто-то не задумывается, но даже тогда я не вижу звукового дизайна, который мог бы работать только потому, что U extends T

Ответ 4

Первый способ

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

означает, что T и U могут быть разных типов. Т.е. один тип T и один тип U который является подтипом T

С вашим вторым примером

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

bar(T t) должен возвращать тот же тип, что и аргумент t. Он не может вернуть объект типа, который является суперклассом, к типу аргумента. Это было бы возможно только с вашим первым вариантом.

Ответ 5

это потенциально полезно, если у подкласса есть определенные функции, которые вы хотите использовать внутри вашего метода, и вы действительно ожидаете, что у этого класса всегда будет подкласс/класс реализации

возьмите следующий пример: T = Person, U = Student (расширяет Person)

В случае

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

Допустим, у вас есть функция foo, которая изменит Person, чтобы указать, что он был в школе, в этом случае вы захотите использовать свойства Student, чтобы проверить это.

в нашем случае во время выполнения это оценило бы

public static Person foo(Student){...}

поэтому у вас не будет необходимости добавлять приведение к ученику в этом коде, чтобы получить доступ к свойствам ученика, но, поскольку ученик - это человек, вы можете просто вернуть измененный U (Персона) обратно

В примере с вашей общедоступной статической T foo (T) {...} вы все равно нуждаетесь в приведении, если вам нужно свойство/функция, специфичная для подтипа.