Использование 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) {...} вы все равно нуждаетесь в приведении, если вам нужно свойство/функция, специфичная для подтипа.