Я не думаю, что то, что вы показали, является «оскорблением». Весь код, в котором используются AbstractFoo
и C3
, по-прежнему полностью типобезопасен, пока не выполняется небезопасное приведение типов. Границы SELF
в AbstractFoo
означают, что код может полагаться на тот факт, что SELF
является подтипом AbstractFoo<SELF>
, но код не может полагаться на тот факт, что AbstractFoo<SELF>
является подтипом SELF
. Так, например, если у AbstractFoo
был метод, который возвращал SELF
, и он был реализован путем возврата this
(что должно было быть возможным, если бы это действительно был «самотип»), он не компилировался:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
Компилятор не позволяет вам скомпилировать это, потому что это небезопасно. Например, запуск этого метода на C3
вернет this
(который на самом деле является экземпляром C3
) как тип C1
, что вызовет исключение приведения к классу в вызывающем коде. Если вы попытались проскользнуть мимо компилятора, используя приведение типа return (SELF)this;
, вы получите предупреждение о непроверенном приведении, что означает, что вы берете на себя ответственность за его небезопасность.
И если ваш AbstractFoo
действительно используется таким образом, который опирается только на тот факт, что SELF extends AbstractFoo<SELF>
(как сказано в ограничении), и не полагается на тот факт, что AbstractFoo<SELF> extends SELF
, тогда почему вас беспокоит "злоупотребление" C3
? Вы все еще можете писать свои классы C1 extends AbstractFoo<C1>
и C2 extends AbstractFoo<C2>
нормально. И если кто-то другой решит написать класс C3 extends AbstractFoo<C1>
, то до тех пор, пока он будет писать его таким образом, чтобы не использовать небезопасное приведение типов, компилятор гарантирует, что он по-прежнему является типобезопасным. Возможно, такой класс не сможет сделать ничего полезного; Я не знаю. Но это все еще безопасно; так почему это проблема?
Причина, по которой рекурсивная граница типа <SELF extends AbstractFoo<SELF>>
не так часто используется, заключается в том, что в большинстве случаев она не более полезна, чем <SELF>
. Например, параметр типа интерфейса Comparable
не имеет привязки. Если кто-то решает написать класс Foo extends Comparable<Bar>
, он может это сделать, и он безопасен по типу, хотя и не очень полезен, потому что в большинстве классов и методов, которые используют Comparable
, у них есть переменная типа <T extends Comparable<? super T>>
, которая требует, чтобы T
был сопоставим с сам, поэтому класс Foo
нельзя использовать в качестве аргумента типа ни в одном из этих мест. Но для кого-то все еще нормально писать Foo extends Comparable<Bar>
, если они хотят.
Единственные места, где рекурсивная граница, такая как <SELF extends AbstractFoo<SELF>>
, находится в месте, которое фактически использует тот факт, что SELF
расширяет AbstractFoo<SELF>
, что довольно редко. Одно место находится в чем-то вроде шаблона построителя, в котором есть методы, возвращающие сам объект, который можно связать в цепочку. Итак, если у вас есть такие методы, как
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF foo() { }
public SELF bar() { }
public SELF baz() { }
}
и у вас было общее значение AbstractFoo<?> x
, вы можете делать такие вещи, как x.foo().bar().baz()
, чего не могли бы делать, если бы оно было объявлено как abstract class AbstractFoo<SELF>
.
В Java Generics нет способа создать параметр типа, который должен быть того же типа, что и текущий реализующий класс. Если бы, гипотетически, существовал такой механизм, который мог бы вызвать сложные проблемы с наследованием:
abstract class AbstractFoo<SELF must be own type> {
public abstract int compareTo(SELF o);
}
class C1 extends AbstractFoo<C1> {
@Override
public int compareTo(C1 o) {
// ...
}
}
class SubC1 extends C1 {
@Override
public int compareTo(/* should it take C1 or SubC1? */) {
// ...
}
}
Здесь SubC1
неявно наследует AbstractFoo<C1>
, но это нарушает договор, согласно которому SELF
должен быть типом реализующего класса. Если SubC1.compareTo()
должен принимать C1
аргумент, то уже неверно, что тип полученной вещи совпадает с типом самого текущего объекта. Если SubC1.compareTo()
может принимать SubC1
аргумент, то он больше не переопределяет C1.compareTo()
, так как он больше не принимает такой широкий набор аргументов, который принимает метод в суперклассе.
person
newacct
schedule
01.02.2020