В последнее время я столкнулся с методом, подобным этому, и я не совсем понимаю его использование:
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, но я до сих пор не знаю, имеет ли первый метод с двумя параметрами какое-либо преимущество перед вторым методом только с одним универсальным.
@Thilo У меня нет настоящего образца кода. Я столкнулся с этим, увидев этот вопрос. Использование там похоже на механизм разрешения Bean, где вы можете определить делегата. Например. вот так AutoBean<Object> autoBean = Foo.<Object, String>getAutoBean("delegate")
. Но это все еще не имеет особого смысла, так как с одним общим параметром результат будет таким же. Передайте Object
и получите Object
.
Это другое. Не возвращается T
, а возвращается Something<T>
.
@RealSkeptic, черт возьми! Вы правы, в таком случае делает имеет смысл (я думаю). Потому что дженерики не являются неявно полиморфными (думаю, так это называется). String
расширяет Object
, но Something<String>
расширяет нетSomething<Object>
Точно. Это означает, что если вы обновите вопрос с этой информацией... он будет закрыт как дубликат.
Это мог, потому что это помогает компилятору решать проблемы логического вывода.
@MCEmperor это все еще можно обойти, используя «подсказки типов», например. Object o = Baz.<Object>.foo("hello")
, но я согласен. Может быть, это просто еще один способ делать что-то, я думаю
@Lino Да, но я все еще не уверен. Я не думаю, что ваш специфический<T, U extends T> T foo(U u)
— это что-то иное, кроме как убрать U
и просто заменить его на T
. Пример, предоставленный Marco13, не совсем совпадает с вашим примером: он использует List<U>
вместо простого U
. Я согласен, что в контексте параметризованный тип (Something<U>
) это будет иметь смысл, но я сомневаюсь, что это также имеет место в контексте тип параметра (U
).
@RealSkeptic Я согласен. Я принял Ответ Марко13, хотя они указали на то, что я допустил ошибку интерпретации (и это тоже просто хороший ответ). Но не стесняйтесь оставлять этот дубликат здесь. В любом случае рад закрыть его, так как мое замешательство было снято
@MCEmperor Я могу только согласиться с тобой. Вероятно, нет никакого варианта использования только простых аргументов с универсальными типами. Параметризованные классы — это другой, но допустимый вариант использования, как показано Marco13.
Моей первой мыслью было: черт возьми,
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 и C. Но когда метод (на самом деле) просто возвращает экземпляр B, зачем вообще здесь нужны дженерики?!
Итак, я согласен с вопросом: я также не вижу значения практичный этой конструкции. Разве что в размышления, но и тогда я не вижу звукового оформления, в котором мог бы работать Только из-за U extends T
.
конечно, есть; введя U
- теперь вы можете вернуть super-type
, с простым T
- вы не можете.
@Eugene Да, я уже понял это и, надеюсь, теперь прояснил.
Но опять же, если он возвращает «только C», вы все равно можете присвоить результат B
, передать его как параметр B
и т. д.
@RealSkeptic Но «только C» НЕ совпадает с «B и C». Вышеупомянутый метод всегда может вернуть «просто B». Который совсем не как Си!
Я думаю, что это ответ. Но я все еще скептически, представьте, что вы метод. И вы получаете параметр типа U
и должны вернуть T
. Вы все еще не знаете точные типы, не так ли? Вы не можете написать return new T()
, потому что вы просто не знаете, что такое T
. Может быть, это сработает, если ввести еще один уровень абстракции. Например. наличие другого класса, который возвращает только экземпляры T
. Хотя может я слишком далеко думаю
Да, я не вижу никакого способа, которым метод мог бы вернуть что-то, что было супертипом, но не подтипом, за исключением случаев, когда он делал что-то в отражении, и тогда он не был бы применим ко всем типам.
@ Лино, ты почти там, ИМО ... возьмем, например, Optional::or
. попробуй создать Optional.ofNullable(<SomeSubType>).or(<SomeSuperType>)
... сможешь?
@Eugene Именно потому, что ofNullable
возвращает не супертип своего параметра, а Optional<T>
.
@RealSkeptic точно. но определение T super U
не допускается; так что это невозможно сделать иначе, если вы не объявите статический метод с определением, как в вопросе.
Нет, @Юджин. Это разные, чем вопрос, он возвращает Something<T>
, а не T
, что означает, что он не полиморфен с Something<U>
, тогда как T
является полиморфен с U
. Посмотрите комментарии к ОП.
@RealSkeptic Я имел в виду Optional::orElse
, мой плохой, который возвращает T
@RealSkeptic Я не говорил, что это имеет большое практическое значение в реальном мире ;-)
@Lino Я только что снова обновил свой ответ. Я согласен с этой "практической" точкой зрения.
@GhostCat Да, как видно из других ответов. Удобство использования аргументов с универсальным типом довольно строгое и не имеет смысла в этом контексте использования. Однако параметризованные классы, такие как List<T>
и List<U>
, — это совсем другая глава, и в некоторых случаях эта конструкция имеет смысл.
@RealSkeptic @Lino посмотрите на этот пример: static class Holder<T> { private final T t; public Holder(T t) { this.t = null; } // such a method would not compile // public <U super T> whenNull(U defaultValue){ // return t == null ? defaultValue : t; // } }
как говорится в комментарии - это невозможно. Вам нужно будет добавить еще один тип, чтобы сделать это через: static class Holder<T, U extends T> {... }
, но это добавляет еще один параметр типа... (продолжение в следующем комментарии)
для таких примеров, как List<T>
или Optional<T>
, это никогда не будет хорошей идеей, просто чтобы иметь возможность возвращать супертип. единственное решение - добавить метод статический с определением вашего выше, чтобы иметь возможность достичь этого.
Первый способ
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
. Он не может возвращать объект типа, который является суперклассом для типа аргумента. Это было бы возможно только с вашим первым вариантом.
Но объект, который он возвращает, можно назначать или использовать в любом контексте, требующем его супертипа.
Это не обязательно правда. На самом деле метод может возвращать объект не является конкретного типа U, но другого конкретного типа X, который также является подклассом T. Могут быть сценарии, в которых это полезно.
@ThomasKainrad, хотя вы действительно не знаете, что такое T
на самом деле. Поскольку весь контекст является общим. Вы не можете быть уверены, что X
В самом деле является подтипом T
Я могу быть уверен, что метод возвращает объект супертипа T
, так как это есть в его сигнатуре. Затем я мог бы проверить через instanceof
, какой это конкретный тип. Это может быть X
и определенно подтип T
.
@ThomasKainrad, использующий instanceof
, в первую очередь побеждает всю цель дженериков. Но да, я согласен, это может быть использование
С моей точки зрения, этот аргумент не о том, имеет ли это смысл или хороший дизайн. Я только хотел указать, что есть сценарии, в которых первый вариант обеспечивает функциональность, недоступную для второго варианта.
Это удобно, когда вы хотите вернуть супер тип; точно так же, как вы показали в своем примере.
Вы принимаете 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>)
Но на самом деле вам это не нужно, потому что U в любом случае можно присвоить T.
Я согласен с @RealSkeptic. Здесь U
по-прежнему можно присвоить T
, поэтому T
должно быть достаточно, поскольку вы можете передать любой подтип указанному методу.
Я думаю, что он прав, соответственно обновлю свой ответ.
Я думаю, что на самом деле этот Только имеет смысл, когда параметр типа метода появляется как параметр типа параметризованного типа, который является частью сигнатуры метода.
(At least, I couldn't quickly come up with an example where it would really makes sense otherwise)
Это также относится к вопросу, на который вы ссылались, где параметры типа метода используются в качестве параметров типа в классе AutoBean
.
A small update:
Основываясь на обсуждении вопроса и других ответов, суть этого вопроса, вероятно, заключалась в неправильном истолковании способа использования параметров типа. Таким образом, этот вопрос можно рассматривать как дубликат Значение <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;
}
}
(Конечно, это выглядит немного надуманным, но я думаю, что можно представить сценарии, в которых это действительно имеет смысл)
Ты прав. В данном случае это как бы имеет смысл. Хотя можно было бы и просто написать public static <T> List<T> withList(List<? extends T> l)
, что сработало бы для numberList = withList(integerList)
. Может быть, это просто какая-то избыточность в языке java
В этом случае это имеет смысл, но в случае примера OP, где полученный параметр имеет тип U
, а не тип Something<U>
, я не могу представить случай, когда это действительно имеет значение.
@MCEmperor на самом деле я неправильно истолковал код где я это видел. В этом вопросе они возвращают параметризованный класс AutoBean<T>
вместо простого T
. Так что это имеет смысл, я думаю
@Lino Действительно, вместо такого U extends T
часто можно (и можно было бы в этом примере) использовать ? extends T
в подписи. Случай, когда это невозможно, например, List<T> copyAndAdd(List<U> us, U anotherU)
, где U
должен быть известен для второго параметра.
Возможно, в вашем упрощенном примере пропущен аспект, в котором этот дополнительный
T
имеет смысл. Можете ли вы связать/включить настоящую подпись?