Как я могу ссылаться на тип класса, который реализует интерфейс на 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, почему бы просто не использовать?