Подтвердить что ты не робот

Избегание общих типов форм Foo <ActualType extends Foo <ActualType>>

Я часто обнаруживаю, что хочу написать общие определения классов формы

public class Foo<ActualType extends Foo<ActualType>>

Например, в настройке, подобной этой:

public interface ChangeHandler<SourceType> {
    public void onChange(SourceType source);
}


public class Foo<ActualType extends Foo<ActualType>> {

    private final List<ChangeHandler<ActualType>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<ActualType> handler) {
        handlers.add(handler);
    }

    @SuppressWarnings("unchecked")
    protected void reportChange() {
        for (ChangeHandler<ActualType> handler: handlers)
            handler.onChange((ActualType) this);
    }
}


public class Bar extends Foo<Bar> {
    // things happen in here that call super.reportChange();
}


public static void main(String[] args) throws IOException {

    Bar bar = new Bar();
    bar.addChangeHandler(new ChangeHandler<Bar>() {

        @Override
        public void onChange(Bar source) {
            // Do something with the changed object
        }
    });
}

Событие change здесь является лишь примером. Это более общая проблема, которая возникает у меня, когда я хотел бы позволить суперклассу предоставлять функциональность, которая "индивидуализирована" для каждого конкретного подкласса (не уверен, как это лучше выразить)... в примере выше "индивидуализации" заключается в том, что ChangeHandler вызывается с объектом фактического подтипа (Bar), а не с типом суперкласса (Foo), вызывающего обработчик).

Как-то этот подход кажется мне немного грязным. И это фактически разрешает потенциальные проблемы, поскольку ничто не мешает мне определить:

public class Baz extends Foo<Bar> { /* ... */ }

Есть ли более чистая альтернатива?

Священный Грааль будет некоторым параметром типа, который всегда определяется как содержащий текущий класс, как статическая версия this.getClass(), которая позволила бы мне написать что-то вроде этого:

public class Foo {

    private final List<ChangeHandler<this.Class>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<this.Class> handler) {
        handlers.add(handler);
    }

    protected void reportChange() {
        for (ChangeHandler<this.Class> handler: handlers)
            handler.onChange(this);
    }
}

Где this.Class будет равно Bar для классов типа Bar.

4b9b3361

Ответ 1

Это действительно абстрактная проблема. На мой взгляд, короткий ответ на вопрос "как сделать этот чище": используйте только дженерики, где это необходимо.

public class List<T extends List<T>>

Что это пытается выразить (заменить)? Список, который позволяет только удерживать (T расширяет) другие списки, которые сами содержат Ts (List), которые, как мы знаем ранее, являются списками, которые позволяют только удерживать... и так далее. Вид кругового, я не понимаю, как вы закончите с чем-то вроде этого?

public interface ChangeHandler<SourceType> {
    public void onChange(SourceType source);
}

Почему вы хотите использовать дженерики здесь? Если вы хотите иметь обработчик изменений, который может обрабатывать несколько типов ресурсов, то вы можете либо создать суперкласс, из которого наследуются все фактические источники, либо создать интерфейс, который реализуется источниками. Подобно этому вы можете точно указать, что выставлено источниками. В качестве альтернативы источник может создать исходный объект при уведомлении вместо передачи "this" (тогда это больше похоже на сообщение). Например:

public interface ChangeHandler {
    public void onChange(Source source);
}

public abstract class Source {
    private List<ChangeHandler> handlers;
    protected int nr;
    public Source(int nr) {
      this.nr = nr;
    }
    public abstract Something getSomething();
    public String toString() {
        return "SRC" + nr;
    }
    ...
    private notify(int index) {
        handlers.get(i).onChange(this);
    }
}

public class Foo extends Source {
    public Foo(int nr) {
        super(nr);
    }
    public String toString() {
        return super.toString() + "Foo";
    }
    public Something getSomething() {
        return new Something();
    }
}

Вам не нужно бросать... или не так ли? Я не уверен, понимаю ли я проблему.

Ответ 2

Я бы рекомендовал, чтобы мы просто использовали <This> для представления типа "self". Нет необходимости связывать, поскольку он выглядит сложным, не доставляет намерения и в любом случае не может принуждать к ограничению.

public class Foo<This> {

    private final List<ChangeHandler<This>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<This> handler) {
        handlers.add(handler);
    }

    @SuppressWarnings("unchecked")
    protected void reportChange() {
        for (ChangeHandler<This> handler: handlers)
            handler.onChange( (This)this );
    }
}

Обратите внимание на листинг (This)this.

См. также Java generics: используйте этот тип как возвращаемый тип?