Метод интерфейса по умолчанию для абстрактного суперкласса

Допустим, у меня есть следующая структура:

abstract class A {
     abstract boolean foo();
}

interface B {
     default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    //function foo
}

Теперь Java будет жаловаться, что класс C должен реализовать абстрактный метод foo из A. Я могу относительно легко обойти эту проблему, переопределив функцию в C и просто вызвав B.super.foo();.

однако я не понимаю, почему функция по умолчанию из интерфейса B сама по себе не выполняет это требование, и я хотел бы лучше понять базовую механику java.


person L0laapk3    schedule 12.05.2017    source источник
comment
Можете ли вы дать некоторый контекст приложения, чтобы объяснить эту структуру? Например, почему класс A не реализует интерфейс B? Тогда классу C нужно просто расширить класс A, чтобы воспользоваться преимуществами обоих супертипов.   -  person Bobulous    schedule 13.05.2017
comment
Это правильный вопрос, и я пока не вижу правильного ответа. Когда мы наследуем, общедоступный метод в суперклассе так же хорош, как и метод, определенный в подклассе. Таким образом, мы можем представить, что метод foo() реализован только в классе C. Так почему компилятор жалуется?   -  person Chirag    schedule 23.04.2021


Ответы (3)


В текущем состоянии вашей программы, если бы вы переопределили A#foo в C.java (где метод по умолчанию возвращает true, а переопределенный метод возвращает false, печать C#foo привела бы к false, полностью игнорируя метод по умолчанию.

Любые абстрактные методы, определенные в абстрактном классе, должны быть переопределены первым конкретным классом, который расширяет абстрактный класс. По этой причине требуется, чтобы C.java переопределяло A#foo.

Методы по умолчанию в интерфейсах не требуется переопределять.

Однако оба метода имеют одинаковые сигнатуры, а это означает, что один из них необходимо переопределить, а другой может переопределить.

Это чрезвычайно плохой дизайн, и его не следует использовать, поскольку методы с одинаковой сигнатурой не могут быть переопределены одновременно. Если вы хотите, чтобы вам требовалось переопределить абстрактный метод, просто измените имя абстрактного метода или метода по умолчанию на что-то другое, кроме foo.

abstract class A {
    abstract boolean bar();
}

interface B {
    default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   

    @Override
    public boolean bar() {
        ...
    }
}

Если вы хотите переопределить только A#foo в некоторых случаях, вы можете просто полностью удалить абстрактный метод и сохранить метод по умолчанию в B.java и переопределить его в C.java:

abstract class A {

}

interface B {
    default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   
}

Если удаление A#foo невозможно, переименуйте метод по умолчанию, B#foo, во что-то другое.

abstract class A {
    abstract boolean foo();
}

interface B {
    default boolean bar() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   

    @Override
    public boolean bar() {
        ...
    }
}
person Jacob G.    schedule 12.05.2017
comment
Хорошее объяснение, но полное удаление A#foo не вариант, так как это, очевидно, означает, что вы больше не можете вызывать foo для своей коллекции A. Тем не менее, я думаю, что нашел возможное решение и для этого, я напишу его в ответе, чтобы было более понятно. - person L0laapk3; 12.05.2017
comment
Если удаление A#foo невозможно, переименуйте метод по умолчанию B#foo во что-то другое. - person Jacob G.; 12.05.2017

Методы по умолчанию в интерфейсах используются для предотвращения поломки программ, зависящих от вашего интерфейса, при добавлении метода в ваш интерфейс.

В случае, который вы описываете, неясно, действительно ли метод по умолчанию в интерфейсе (который по замыслу должен был быть добавлен позже) действительно выполняет контракт, как первоначально предполагалось абстрактным классом.

В этом случае для Java безопаснее жаловаться.

Вышеприведенный текст является моей интерпретацией абзаца в 9.4.1.3. спецификации JLS SE8, и я цитирую:

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

person Maarten van Leunen    schedule 26.05.2017

Ответ Jacob G. вдохновил меня на это решение:

interface Z {
     abstract boolean foo();
}

abstract class A implements Z {

}

interface B extends Z {
     default boolean foo() { return doBlah(); }
}

class C extends A implements B {

}

Таким образом, все подклассы класса A должны определять метод foo(), не требуя, чтобы каждый класс, реализующий B, повторно реализовывал его.

person L0laapk3    schedule 12.05.2017
comment
Класс A все еще существует, потому что он содержит другие методы и переменные, которые нельзя реализовать в интерфейсе. - person L0laapk3; 12.05.2017