Использование T расширяет U? - PullRequest
17 голосов
/ 27 мая 2019

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

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 параметрами какое-либо преимущество перед вторым методом только с одним универсальным .

Ответы [ 4 ]

9 голосов
/ 27 мая 2019

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

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

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


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

На основе обсуждения ввопрос и другие ответы, суть этого вопроса, вероятно, была неверной интерпретацией способа использования параметров типа.Таким образом, этот вопрос может рассматриваться как дубликат Значениев объявлении Java-функции , но, надеюсь, кто-то сочтет этот ответ полезным, тем не менее.

В конце концов, причина использования шаблона <T, U extends T> может быть видна в отношениях наследования параметризованных типов, которыев деталях может быть довольно сложным .В качестве примера, чтобы проиллюстрировать наиболее важный момент: A 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;
    }

}

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

4 голосов
/ 27 мая 2019

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

Вы берете 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>)
2 голосов
/ 27 мая 2019

Первый метод

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. Он не может вернуть объект типа, который является суперклассом, к типу аргумента. Это было бы возможно только с вашим первым вариантом.

2 голосов
/ 27 мая 2019

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

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...