Как я могу ссылаться на тип класса, который реализует интерфейс на Java?
У меня возникла проблема с интерфейсами в программе, которую я делаю. Я хочу создать интерфейс, который имеет один из его методов, получающий/возвращающий ссылку на тип собственного объекта. Это было что-то вроде:
public interface I {
? getSelf();
}
public class A implements I {
A getSelf() {
return this;
}
}
public class B implements I {
B getSelf() {
return this;
}
}
Я не могу использовать "I", где он "?", потому что я не хочу возвращать ссылку на интерфейс, но класс. Я искал и обнаружил, что в Java нет возможности "самостоятельно ссылаться", поэтому я не могу просто заменить это "?" в примере для ключевого слова "Я" или что-то вроде этого. Фактически, я подошел к решению, которое выглядит как
public interface I<SELF> {
SELF getSelf();
}
public class A implements I<A> {
A getSelf() {
return this;
}
}
public class B implements I<B> {
B getSelf() {
return this;
}
}
Но это действительно похоже на обходное решение или что-то подобное. Есть ли другой способ сделать это?
Ответы
Ответ 1
Существует способ принудительного использования собственного класса в качестве параметра при расширении интерфейса:
interface I<SELF extends I<SELF>> {
SELF getSelf();
}
class A implements I<A> {
A getSelf() {
return this;
}
}
class B implements I<A> { // illegal: Bound mismatch
A getSelf() {
return this;
}
}
Это даже работает при написании родовых классов. Единственный недостаток: нужно приложить this
к SELF
.
Как отметил Андрей Макаров в комментарии ниже, это делает не надёжную работу при написании родовых классов.
class A<SELF extends A<SELF>> {
SELF getSelf() {
return (SELF)this;
}
}
class C extends A<B> {} // Does not fail.
// C myC = new C();
// B myB = myC.getSelf(); // <-- ClassCastException
Ответ 2
Java поддерживает ковариантные типы возврата, поэтому один параметр. Воспользуйтесь тем, что как A
, так и B
получены из Object
:
public interface I {
Object getSelf(); // or I, see below
}
public class A implements I {
A getSelf() { return this; }
}
public class B implements I {
B getSelf() { return this; }
}
Дело в том, что оба A.getSelf()
и B.getSelf()
являются законными переопределениями I.getSelf()
, хотя их тип возврата отличается. Это потому, что каждый A
можно рассматривать как Object
, поэтому возвращаемый тип совместим с типом базовой функции. (Это называется "ковариация".)
Фактически, поскольку A
и B
также известны из I
, вы можете заменить Object
на I
по тем же причинам.
Ковариация - это, как правило, хорошая вещь. Кто-то, у кого есть объект интерфейса типа I
, может вызвать getSelf()
и получить другой интерфейс, и все, что ей нужно знать. С другой стороны, кто-то, кто уже знает, что у него есть объект A
, может вызвать getSelf()
и фактически получит еще один объект A
. Дополнительная информация может использоваться для получения более определенного производного типа, но у кого-то, у кого нет этой информации, все еще есть все, что предписывается базовым классом интерфейса:
I x = new A();
A y = new A();
I a = x.foo(); // generic
A b = y.foo(); // we have more information, but b also "is-an" I
A c = (A)x.foo(); // "cheating" (we know the actual type)
Ответ 3
Мне было интересно, есть ли другой способ сделать это?
Вы можете записать его следующим образом:
public interface I {
I getSelf();
}
а затем приведите результат к типу, который вы хотите. Существующие классы A
и B
будут работать как есть.
(Это пример ковариации возвращаемого типа. Поддержка ковариации возвращаемого типа была добавлена в Java 5. Этот подход даст ошибки компиляции в старых JDK.)
Альтернативная версия (вы называете ее обходной, но это не так), которая использует generics, позволяет избежать явного приведения типов. Однако в сгенерированном коде присутствует неявный тип, а во время выполнения... если компилятор JIT не может оптимизировать его.
Нет лучших альтернатив, AFAIK.
Ответ 4
Как уже говорили другие, вы можете переопределить тип возвращаемого значения в классах реализации:
public interface I {
public I getSelf();
}
public class A implements I {
@Override
public A getSelf() {
return this;
}
}
Однако у меня есть два вопроса "почему":
1: Почему вы хотите, чтобы интерфейс возвращал объект реализации? Кажется, это противоречит общим идеям интерфейсов и наследованию для меня. Можете ли вы показать пример того, как это можно использовать?
2: В любом случае, зачем вам нужна эта функция? Если a.getSelf() == a, почему бы просто не использовать?