Как я могу сослаться на тип класса, который интерфейс реализует в Java? - PullRequest
17 голосов
/ 17 ноября 2011

У меня возникла проблема с интерфейсами в программе, которую я создаю. Я хочу создать интерфейс, который имеет один из методов, получающих / возвращающих ссылку на тип собственного объекта. Это было что-то вроде:

public interface I {
    ? getSelf();
}

public class A implements I {
    A getSelf() {
        return this;
    }
}

public class B implements I {
    B getSelf() {
        return this;
    }
}

Я не могу использовать «I», где это «?», Потому что я не хочу возвращать ссылку на интерфейс, а на класс. Я искал и обнаружил, что в Java нет способа «самореференции», поэтому я не могу просто заменить это «?» в примере для ключевого слова "self" или что-то вроде этого. На самом деле, я пришел к решению, которое выглядит как

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;
    }
}

Но это действительно похоже на обходной путь или что-то в этом роде. Есть ли другой способ сделать это?

Ответы [ 4 ]

11 голосов
/ 09 мая 2013

Существует способ принудительно использовать собственный класс в качестве параметра при расширении интерфейса:

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
5 голосов
/ 17 ноября 2011

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)
2 голосов
/ 17 ноября 2011

Мне было интересно, есть ли другой способ сделать это?

Вы можете написать это следующим образом:

public interface I {
   I getSelf();
}

а затем приведите результат к нужному типу.Ваши существующие классы A и B будут работать как есть.

(Это пример ковариации возвращаемого типа. В Java 5 была добавлена ​​поддержка ковариации возвращаемого типа. Этот подход даст ошибки компиляции в старых JDK.)

Альтернативная версия (вы вызываетеэто обходной путь, но на самом деле это не так), который использует дженерики, позволяет избежать явного преобразования типов.Однако в сгенерированном коде есть неявное приведение типов, и во время выполнения ... если JIT-компилятор не сможет его оптимизировать.

Нет лучших альтернатив, AFAIK.

1 голос
/ 17 ноября 2011

Как уже говорили другие, вы можете переопределить тип возвращаемого значения в реализующих классах:

public interface I {
    public I getSelf();
}

public class A implements I {
    @Override
    public A getSelf() {
        return this;
    }
}

Однако у меня есть два вопроса «почему» для вас:

1: Почему вы хотите, чтобы интерфейс возвращал объект реализации? Кажется, это противоречит общим представлениям об интерфейсах и наследовании. Можете ли вы показать пример того, как это можно использовать?

2: В любом случае, зачем вам эта функция? Если a.getSelf () == a, почему бы просто не использовать a?

...