Реализация одного и того же интерфейса для двух разных типов в одном классе

299
16 января 2017, 19:31

Есть некий интерфейс:

public interface ViewableParent<C extends AfdxObject> extends Viewable{
    interface ListChangeCallback<C>{
        void doWhenChange(C object);
    }
    ListChangeCallback<C> getAddedSubListCallback();
    ListChangeCallback<C> getRemovedSubListCallback();
    default ListChangeListener<C> getChangeListener(){
        return c -> {
            while (c.next()){
                if (c.wasAdded()) {
                    //Проходимся по добавленным элементам
                    c.getAddedSubList().forEach(e -> {
                        //Вешаем на проперти стиля слушатель, который при изменении стиля потомка меняет стиль у родителя
                        e.styleProperty().addListener((observable, oldValue, newValue) ->
                                refreshStyle(c.getList(), ViewableParent.this,false));
                        //Выполяняем код add коллбэка
                        if (getAddedSubListCallback()!=null)
                            getAddedSubListCallback().doWhenChange(e);
                    });
                    //Пересчитываем стиль для родителя исходя из стилей добавляемых потомков
                    refreshStyle(c.getAddedSubList(), ViewableParent.this,true);
                } else if (c.wasRemoved() || c.wasUpdated()) {
                    //Иначе, если что-то было удалено или обновлено (здесь не уверен в корректности события update)
                    //Так же пересчитываем стиль для родителя
                    refreshStyle(c.getList(), ViewableParent.this, false);
                    //Выполняем код remove коллбэка
                    if (getRemovedSubListCallback()!=null) 
                        c.getRemoved().forEach(e -> getRemovedSubListCallback().doWhenChange(e));                    
                }
            }
        };
    }
}

Параметр типа C обозначает тип потомка для класса, реализующего данный интерфейс. 4 из 5 классов имеют только один тип потомка, однако есть один класс, в котором потомков 2. Если в объявлении класса 2 раза написать один и тот же интерфейс с разными типами в дженерике, само собой IDE выдает ошибку. Подскажите, каким образом можно обойти данное ограничение с точки зрения архитектуры?

Answer 1

У вас не получится реализовать два раза интерфейс с разными параметризированными типами:

interface MyInterface<T>{
   T method();
}
class MyClass implements MyInterface<A>, MyInterface<B>{
    ...
}

Потому, что происходит стирание типов, и в байткоде остаются только интерфейсы с типом Object. Т.е. для java MyInterface<A> и MyInterface<B> одно и тоже.

Если бы даже, не происходило стирания, то возникли бы логические ошибки. Как ранее заметил @zRrr может возникнуть ситуация, что класс реализует два одинаковых метода, с разным возвращаемым типом.

public A method(){
  ...
}
public B method(){
  ...
}

При вызове которых, не понятно, что вызывать, и в java такой код не является корректным.

Если очень хочется, то можно сделать, но опять же, зависит от сигнатур методов интерфейса и не во всех случаях будет работать. Для интерфейса Comparable это выглядит следующим образом:

class A implements Comparable<A> {
    @Override
    public int compareTo(A o) {
        return 0;
    }
}
class B implements Comparable<B> {
    @Override
    public int compareTo(B o) {
        return 0;
    }
}
//E extends A or B
class Gibrid<E> implements Comparable<E> {
    final A a = new A();
    final B b = new B();
    @Override
    public int compareTo(E o) {
        if (o instanceof A)
            return a.compareTo((A) o);
        else if (o instanceof B)
            return b.compareTo((B) o);
        throw new IllegalArgumentException("object type is not correct."
                                                        +"It must be A or B");
    }
}

Но здесь мы уходим от проверки типов во время компиляции во время исполнения программы.

Answer 2

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

public interface ViewableParent extends Viewable{
    interface ListChangeCallback<C>{
        void doWhenChange(C object);
    }
    default <C extends Viewable> ListChangeListener<C> getViewChangeListener(ListChangeCallback<C> addCallback, ListChangeCallback<C> removeCallback){
        return c -> {
            while (c.next()){
                if (c.wasAdded()) {
                    //Проходимся по добавленным элементам
                    c.getAddedSubList().forEach(e -> {
                        //Вешаем на проперти стиля слушатель, который при изменении стиля потомка меняет стиль у родителя
                        e.styleProperty().addListener((observable, oldValue, newValue) ->
                                refreshStyle(c.getList(),false));
                        //Выполяняем код add коллбэка
                        if (addCallback!=null)
                            addCallback.doWhenChange(e);
                    });
                    //Пересчитываем стиль для родителя исходя из стилей добавляемых потомков
                    refreshStyle(c.getAddedSubList(),true);
                } else if (c.wasRemoved() || c.wasUpdated()) {
                    //Иначе, если что-то было удалено или обновлено (здесь не уверен в корректности события update)
                    //Так же пересчитываем стиль для родителя
                    refreshStyle(c.getList(),false);
                    //Выполняем код remove коллбэка
                    if (removeCallback!=null)
                        c.getRemoved().forEach(removeCallback::doWhenChange);
                }
            }
        };
    }
}
READ ALSO
Получить количество совпадений AutoComleteTextView

Получить количество совпадений AutoComleteTextView

Имею AutoCompleteTextViewХочу изменить цвет текста, если нет совпадений

258
SearchView null pointer

SearchView null pointer

SearchView не получается инициализировать, выдает null:

291
Создание сайта путем &ldquo;рисования&rdquo; его в Adobe Illustrator(AI)

Создание сайта путем “рисования” его в Adobe Illustrator(AI)

Как это происходит? Сайты состоят из html кода, css и js(я про визуальную часть)

283
Почему JS код блокирует submit

Почему JS код блокирует submit

Есть форма на ASPNET MVC

268