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

Возможно, в вашем упрощенном примере пропущен аспект, в котором этот дополнительный T имеет смысл. Можете ли вы связать/включить настоящую подпись?

Thilo 27.05.2019 13:16

@Thilo У меня нет настоящего образца кода. Я столкнулся с этим, увидев этот вопрос. Использование там похоже на механизм разрешения Bean, где вы можете определить делегата. Например. вот так AutoBean<Object> autoBean = Foo.<Object, String>getAutoBean("delegate"). Но это все еще не имеет особого смысла, так как с одним общим параметром результат будет таким же. Передайте Object и получите Object.

Lino 27.05.2019 13:24

Это другое. Не возвращается T, а возвращается Something<T>.

RealSkeptic 27.05.2019 13:27

@RealSkeptic, черт возьми! Вы правы, в таком случае делает имеет смысл (я думаю). Потому что дженерики не являются неявно полиморфными (думаю, так это называется). String расширяет Object, но Something<String> расширяет нетSomething<Object>

Lino 27.05.2019 13:34

Точно. Это означает, что если вы обновите вопрос с этой информацией... он будет закрыт как дубликат.

RealSkeptic 27.05.2019 13:37

Это мог, потому что это помогает компилятору решать проблемы логического вывода.

MC Emperor 27.05.2019 13:44

@MCEmperor это все еще можно обойти, используя «подсказки типов», например. Object o = Baz.<Object>.foo("hello"), но я согласен. Может быть, это просто еще один способ делать что-то, я думаю

Lino 27.05.2019 13:51

@Lino Да, но я все еще не уверен. Я не думаю, что ваш специфический<T, U extends T> T foo(U u) — это что-то иное, кроме как убрать U и просто заменить его на T. Пример, предоставленный Marco13, не совсем совпадает с вашим примером: он использует List<U> вместо простого U. Я согласен, что в контексте параметризованный тип (Something<U>) это будет иметь смысл, но я сомневаюсь, что это также имеет место в контексте тип параметра (U).

MC Emperor 27.05.2019 13:57

@RealSkeptic Я согласен. Я принял Ответ Марко13, хотя они указали на то, что я допустил ошибку интерпретации (и это тоже просто хороший ответ). Но не стесняйтесь оставлять этот дубликат здесь. В любом случае рад закрыть его, так как мое замешательство было снято

Lino 27.05.2019 13:58

@MCEmperor Я могу только согласиться с тобой. Вероятно, нет никакого варианта использования только простых аргументов с универсальными типами. Параметризованные классы — это другой, но допустимый вариант использования, как показано Marco13.

Lino 27.05.2019 14:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
18
10
2 424
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Моей первой мыслью было: черт возьми,

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 27.05.2019 13:24

@Eugene Да, я уже понял это и, надеюсь, теперь прояснил.

GhostCat 27.05.2019 13:33

Но опять же, если он возвращает «только C», вы все равно можете присвоить результат B, передать его как параметр B и т. д.

RealSkeptic 27.05.2019 13:36

@RealSkeptic Но «только C» НЕ совпадает с «B и C». Вышеупомянутый метод всегда может вернуть «просто B». Который совсем не как Си!

GhostCat 27.05.2019 13:37

Я думаю, что это ответ. Но я все еще скептически, представьте, что вы метод. И вы получаете параметр типа U и должны вернуть T. Вы все еще не знаете точные типы, не так ли? Вы не можете написать return new T(), потому что вы просто не знаете, что такое T. Может быть, это сработает, если ввести еще один уровень абстракции. Например. наличие другого класса, который возвращает только экземпляры T. Хотя может я слишком далеко думаю

Lino 27.05.2019 13:38

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

RealSkeptic 27.05.2019 13:41

@ Лино, ты почти там, ИМО ... возьмем, например, Optional::or. попробуй создать Optional.ofNullable(<SomeSubType>).or(<SomeSuperType>)... сможешь?

Eugene 27.05.2019 13:42

@Eugene Именно потому, что ofNullable возвращает не супертип своего параметра, а Optional<T>.

RealSkeptic 27.05.2019 13:48

@RealSkeptic точно. но определение T super U не допускается; так что это невозможно сделать иначе, если вы не объявите статический метод с определением, как в вопросе.

Eugene 27.05.2019 13:50

Нет, @Юджин. Это разные, чем вопрос, он возвращает Something<T>, а не T, что означает, что он не полиморфен с Something<U>, тогда как Tявляется полиморфен с U. Посмотрите комментарии к ОП.

RealSkeptic 27.05.2019 13:51

@RealSkeptic Я имел в виду Optional::orElse, мой плохой, который возвращает T

Eugene 27.05.2019 13:55

@RealSkeptic Я не говорил, что это имеет большое практическое значение в реальном мире ;-)

GhostCat 27.05.2019 13:56

@Lino Я только что снова обновил свой ответ. Я согласен с этой "практической" точкой зрения.

GhostCat 27.05.2019 14:04

@GhostCat Да, как видно из других ответов. Удобство использования аргументов с универсальным типом довольно строгое и не имеет смысла в этом контексте использования. Однако параметризованные классы, такие как List<T> и List<U>, — это совсем другая глава, и в некоторых случаях эта конструкция имеет смысл.

Lino 27.05.2019 14:07

@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> {... }, но это добавляет еще один параметр типа... (продолжение в следующем комментарии)

Eugene 27.05.2019 14:15

для таких примеров, как List<T> или Optional<T>, это никогда не будет хорошей идеей, просто чтобы иметь возможность возвращать супертип. единственное решение - добавить метод статический с определением вашего выше, чтобы иметь возможность достичь этого.

Eugene 27.05.2019 14:16

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

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

Но объект, который он возвращает, можно назначать или использовать в любом контексте, требующем его супертипа.

RealSkeptic 27.05.2019 13:26

Это не обязательно правда. На самом деле метод может возвращать объект не является конкретного типа U, но другого конкретного типа X, который также является подклассом T. Могут быть сценарии, в которых это полезно.

Thomas Kainrad 27.05.2019 13:37

@ThomasKainrad, хотя вы действительно не знаете, что такое T на самом деле. Поскольку весь контекст является общим. Вы не можете быть уверены, что XВ самом деле является подтипом T

Lino 27.05.2019 13:40

Я могу быть уверен, что метод возвращает объект супертипа T, так как это есть в его сигнатуре. Затем я мог бы проверить через instanceof, какой это конкретный тип. Это может быть X и определенно подтип T.

Thomas Kainrad 27.05.2019 13:44

@ThomasKainrad, использующий instanceof, в первую очередь побеждает всю цель дженериков. Но да, я согласен, это может быть использование

Lino 27.05.2019 13:49

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

Thomas Kainrad 27.05.2019 14:00

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

Вы принимаете 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 27.05.2019 13:25

Я согласен с @RealSkeptic. Здесь U по-прежнему можно присвоить T, поэтому T должно быть достаточно, поскольку вы можете передать любой подтип указанному методу.

Lino 27.05.2019 13:29

Я думаю, что он прав, соответственно обновлю свой ответ.

GhostCat 27.05.2019 13:31
Ответ принят как подходящий

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

(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

Lino 27.05.2019 13:48

В этом случае это имеет смысл, но в случае примера OP, где полученный параметр имеет тип U, а не тип Something<U>, я не могу представить случай, когда это действительно имеет значение.

MC Emperor 27.05.2019 13:52

@MCEmperor на самом деле я неправильно истолковал код где я это видел. В этом вопросе они возвращают параметризованный класс AutoBean<T> вместо простого T. Так что это имеет смысл, я думаю

Lino 27.05.2019 13:54

@Lino Действительно, вместо такого U extends T часто можно (и можно было бы в этом примере) использовать ? extends T в подписи. Случай, когда это невозможно, например, List<T> copyAndAdd(List<U> us, U anotherU), где U должен быть известен для второго параметра.

Marco13 27.05.2019 13:57

Другие вопросы по теме