Дженерики Java: использовать этот тип в качестве типа возврата?

Я пытаюсь сделать API максимально удобным для пользователя.

Давайте:

class B extends A {}

class A {
    A setX(){ ...; return this; }
}

Теперь это

B b = new B().setX();

недействителен, должен быть приведен:

B b = (B) new B().setX();

Есть ли способ использовать дженерики в A, чтобы компилятор знал об «этом» типе и принимал первый способ - без приведения и без передачи параметра типа в том месте, где он используется? (Т.е. не new B<B>().setX(), это некрасиво.)

Я ЗНАЮ, почему в данном случае нужно перепечатать Java. Пожалуйста, не отвечайте, объясняя, что setX() возвращает A. Я это знаю. Я спрашиваю, могут ли дженерики решить эту проблему.

И для тех, кто все еще хочет сказать мне, что «так работает статическая типизация» и «даже дженерики не могут помочь с этим», рассмотрите этот действительный код Java:

Map<String, String> map = new HashMap(){{ put( "foo", new RuntimeException() );
String foo = map.get("foo");  // ClassCastException!!

Таким образом, вы можете видеть, что дженерики ДЕЙСТВИТЕЛЬНО позволяют вам получить CCE без фактического приведения типов в коде.
Вот почему я ожидаю, что дженерики позволят избавиться от явного приведения типов.

Кроме того, IIRC C++ допускает это.


person Ondra Žižka    schedule 16.06.2013    source источник
comment
Проблема здесь в том, что подкласс не может содержать ссылку на объект, объявленную суперклассом, для этого обязательно требуется понижение. Тут даже дженерики не помогут.   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Они могли бы, потенциально. Само по себе понижение не требуется.   -  person Ondra Žižka    schedule 17.06.2013
comment
Ну, вы могли бы сказать <T extends A> T setX() { return (T)this; }, но обратите внимание, что здесь вы опускаетесь.   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Попробовал, еще нужен гипс...   -  person Ondra Žižka    schedule 17.06.2013
comment
Вы не можете гарантировать компилятору, что метод вернет объект B, и поэтому его необходимо будет выполнить. Привыкайте к этому.   -  person Hovercraft Full Of Eels    schedule 17.06.2013
comment
Пожалуйста, не добавляйте Java и Generic в заголовок. Вопрос уже помечен обоими. Или вы думаете, что любой не-Java-программист попытается ответить на этот вопрос?   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Используя дженерики, вы также подавляете такие приведения. Это просто еще один вариант их использования. Должен быть разрешен, если это не так.   -  person Ondra Žižka    schedule 17.06.2013
comment
Дженерики не подавляют это.   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Я удаляю вопрос и опубликую его повторно, когда избавлюсь от того, кто переименовывает мои вопросы.   -  person Ondra Žižka    schedule 17.06.2013
comment
Обратитесь к мета: Должны ли вопросы включать «теги» в свои заголовки?. Не то чтобы я имел что-то против тебя =\. Пожалуйста, вернитесь после того, как вырастете.   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Обратитесь к реальным стандартам и посмотрите на право. Все включают Java и дженерики в связанные вопросы. Идите вперед и переименуйте их.   -  person Ondra Žižka    schedule 17.06.2013
comment
@OndraŽižka это потому, что многие люди, которые это делают, являются новичками на сайте и не читают мета (просто для вас: это правила на этом сайте).   -  person Luiggi Mendoza    schedule 17.06.2013
comment
Это конкретное правило неверно, потому что, если я посмотрю на список вопросов, я ценю наличие основного контекста в заголовке. Читать теги неприятно.   -  person Ondra Žižka    schedule 17.06.2013
comment
Здесь Typescript имеет желаемую концепцию (typescriptlang.org/docs /handbook/), поэтому этот вопрос действителен, как написано.   -  person Luke Machowski    schedule 22.07.2018


Ответы (4)


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

Цепочка методов

Вместо того, чтобы писать

foo.doA();
foo.doB();

многие люди предпочли бы написать

foo.doA().doB();

К сожалению, язык не поддерживает цепочку методов напрямую, хотя она становится все более востребованной функцией. Обходной путь заключается в том, что doA() возвращает foo. Это немного грязно, но приемлемо.

Однако, если foo находится в иерархии типов, обходной путь не работает.

class Bar
    Bar doA()

class Foo extends Bar
    Foo doB();

foo.doA().doB(); // doesn't compile, since doA() returns Bar

Поэтому некоторые люди призывают к специальному «я-типу» для решения этой проблемы. Допустим, есть ключевое слово This для представления "самотипа".

class Bar
    This doA()

foo.doA().doB(); // works, doA() returns the type of foo, which is Foo

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

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

class Bar<This>
    This doA()

class Foo extends Bar<Foo>

Foo has a method "Foo doA()", inherited from Bar<Foo>

Это самый популярный вариант использования шаблона A extends B<A>. Это изолированный обходной путь/трюк. Он не добавляет семантики в отношения между A и B.

Также популярной практикой является ограничение This наподобие

class Bar<This extends Bar<This>>

Это уродливо и бесполезно, я настоятельно рекомендую против этого. Просто используйте «Это» как условное обозначение, для чего оно предназначено.

person ZhongYu    schedule 16.06.2013
comment
Спасибо за пояснение, нервов писать не хватило. Отличная работа. - person Ondra Žižka; 17.06.2013
comment
Я не думаю, что class Bar<This> { This doA() { return this; } } может скомпилироваться. В противном случае вы можете выполнить new Bar<String>().doA(), что вернет Bar<String> как String. Итак, вам нужно Bar<This extends Bar<This>> - person Troy Daniels; 04.12.2015
comment
@TroyDaniels - да, это добавит некоторые ограничения к This; к сожалению, ограничение недостаточно сильное. вы можете иметь Zoo extends Bar<Foo> где Foo extends Bar<Foo>. Zoo явно ошибка! На практике, однако, я не думаю, что это имеет значение, такие ошибки совершаются редко; именно поэтому я бы полностью отказался от ограничения (в большинстве случаев использования). Программисты интуитивно не будут писать Bar<String>, потому что String не Bar. - person ZhongYu; 09.12.2015

Попробуйте это в классе A:

public <T extends A> T setX() {
    return (T) this;
}

И вы можете использовать его так

B b = new B().setX();
person Golyo    schedule 18.06.2014
comment
Это действительно отличный трюк! Я никогда не мог представить, что это пройдет (вы можете назначить любой неправильный подтип, как в B = a.setX(), C = a.setX() - person Whimusical; 30.12.2015
comment
Однако это дает предупреждение о непроверенном приведении. - person mirabilos; 21.11.2018
comment
Чтобы избежать предупреждения о непроверенном приведении (поскольку в этом случае мы можем точно знать, что приведение правильное, даже если компилятор этого не знает), используйте: ) финальное Т, что = (Т) это; вернуть это; } ` - person mirabilos; 22.11.2018

Если вам действительно не нравится приведение, вы можете позволить B переопределить метод.

@Override
public B setX() {
    super.setX();
    return this;
}
person Bhesh Gurung    schedule 16.06.2013
comment
Спасибо, это то, что я делаю сейчас. Я намерен сделать API как можно более удобным для пользователя, поэтому небольшая работа с моей стороны — это нормально. Но все равно... :) - person Ondra Žižka; 17.06.2013
comment
Конечно, если это поможет сделать ваш API лучше, оно того стоит. - person Bhesh Gurung; 17.06.2013

Проблема в том, что метод setX() возвращает объект класса A, а вы пытаетесь записать его в объект класса B. Это можно сделать и наоборот без приведения (A a = new B();), но таким образом вы должны привести его, потому что оператор B b = new B().setX(); похож на B b = new A();, который не может быть выполнен.

person Misa Lazovic    schedule 16.06.2013
comment
Пожалуйста, ребята. Я знаю основы Java. Я ищу способ преодолеть этот нежелательный эффект, чтобы сократить код. Очевидно, что это тип B. Я могу это сказать, вы можете это сказать, поэтому компилятор тоже должен быть в состоянии это сказать. Вопрос в том, способна ли Java на это? - person Ondra Žižka; 17.06.2013
comment
@OndraŽižka, если вы знаете основы Java как вы сказали, то вы должны знать, что это невозможно. - person Luiggi Mendoza; 17.06.2013
comment
@OndraŽižka, тогда у вас есть комментарий и ответ (который в основном говорит то же, что и мой комментарий), что вы не можете этого сделать. Обратите внимание, что дженерики не могут быть частью основ, но они основаны на основах. Похоже, вы действительно не поняли основы с самого начала... - person Luiggi Mendoza; 17.06.2013
comment
@OndraŽižka Тот факт, что дженерики не являются основными, является оправданием. Нет никакой причины, по которой эта функция может быть даже в рамках дженериков — она могла быть там в Java 1.0 с каким-то ключевым словом thisclass. Его просто нет в Java в целом. Это как спрашивать, почему печенья нет в том ящике, куда вы не можете дотянуться, когда причина, по которой печенья нет во всем доме, в том, что никто никогда не покупал печенье. - person millimoose; 17.06.2013
comment
@Ховеркрафт. Это зависит от того, что именно вы имеете в виду. Очевидно, это невозможно сделать с типом времени выполнения, но я не уверен, что это нельзя сделать статически. Дженерики безумно сложны. - person Antimony; 17.06.2013
comment
@Antimony это можно сделать статически. Посмотрите на ответ BheshGurung (конечно, вы получите много громоздкого кода). - person Luiggi Mendoza; 17.06.2013