Каковы различия между абстрактными классами и интерфейсами в Java 8?

В Java раньше было тонкое, но важное различие между абстрактными классами и интерфейсами: реализация по умолчанию. Абстрактные классы могли иметь их, интерфейсы не могли. Java 8 хотя и вводит реализации по умолчанию для интерфейсов, то есть это уже не критическое различие между интерфейсом и абстрактным классом.

Итак, что?

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

Как новые интерфейсы Java 8 избегают проблемы с алмазом ?

Ответ 1

Интерфейсы не могут иметь связанное с ними состояние.

Абстрактные классы могут иметь состояние, связанное с ними.

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

И, наконец, в случае возникновения проблемы с алмазом компилятор предупредит вас, и вам нужно будет выбрать интерфейс, который вы хотите реализовать.

Чтобы узнать больше о проблеме с алмазом, рассмотрите следующий код:

interface A {
    void method();
}

interface B extends A {
    @Override
    default void method() {
        System.out.println("B");
    }
}

interface C extends A { 
    @Override
    default void method() {
        System.out.println("C");
    }
}

interface D extends B, C {

}

Здесь я получаю ошибку компилятора на interface D extends B, C, что:

interface D inherits unrelated defaults for method() form types B and C

Исправление:

interface D extends B, C {
    @Override
    default void method() {
        B.super.method();
    }
}

В случае, если я хотел наследовать method() от B.
То же самое верно, если D были class.

Чтобы еще больше показать разницу между интерфейсами и абстрактными классами в Java 8, рассмотрите следующие Team:

interface Player {

}

interface Team {
    void addPlayer(Player player);
}

Теоретически вы можете предоставить реализацию addPlayer по умолчанию, чтобы вы могли добавлять игроков к примеру списка игроков.
Но подождите...?
Как сохранить список игроков? Ответ заключается в том, что вы не можете сделать это в интерфейсе, даже если у вас есть доступные по умолчанию реализации.

Ответ 2

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

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

Ответ 3

Определение проблемы алмаза является неопределенным. Существуют всевозможные проблемы, которые могут возникать при множественном наследовании. К счастью, большинство из них можно легко обнаружить во время компиляции, а языки программирования поддерживают простые решения для решения этих проблем. Большинство из этих проблем даже не связаны с проблемой алмаза. Например, конфликтующие определения методов также могут возникать без алмазов:

interface Bar {
    default int test() { return 42; }
}
interface Baz {
    default int test() { return 6 * 9; }
}
class Foo implements Bar, Baz { }

Конкретная проблема с алмазами - вопрос инклюзивного и эксклюзивного. Если у вас есть иерархия типов, где B и C получены из A, а D происходит от B и C, тогда возникает вопрос:

  • - это D a B * и * a C (т.е. один тип A), или
  • - это D a B * или * a C (т.е. два типа A).

Ну, в Java 8 тип A должен быть интерфейсом. Так что это не имеет никакого значения, потому что интерфейсы не имеют состояния. Не имеет значения, что интерфейсы могут определять методы по умолчанию, так как они также не имеют состояния. Они могут ссылаться на методы, которые имеют прямой доступ к состоянию. Однако эти методы всегда реализуются на основе единого наследования.

Ответ 4

Теперь, когда интерфейсы могут содержать исполняемый код, множество прецедентов для абстрактных классов захватываются интерфейсами. Но абстрактные классы могут по-прежнему иметь переменные-члены, а интерфейсы не могут.

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

Ответ 5

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

Тем не менее, существует еще несколько критических различий. Обратитесь к этому сообщению:

Интерфейс с методами по умолчанию vs Абстрактный класс в Java 8

Как новые интерфейсы Java 8 избегают проблемы с алмазом?

Случай 1: Вы реализуете два интерфейса, которые имеют один и тот же метод default, вам нужно разрешить конфликт в классе реализации

interface interfaceA{
    default public void foo(){
        System.out.println("InterfaceA foo");
    }
}
interface interfaceB{
    default public void foo(){
        System.out.println("InterfaceB foo");
    }
}
public class DiamondExample implements interfaceA,interfaceB{
    public void foo(){
        interfaceA.super.foo();
    }
    public static void main(String args[]){
        new DiamondExample().foo();
    }
} 

Ниже приведен пример ниже outout:

InterfaceA foo

Случай 2:. Вы расширяете базовый класс и реализуете интерфейс со стандартным методом. Компилятор разрешает проблему с алмазами для вас, и вам не нужно ее разрешать, как в первом примере.

interface interfaceA{
    default public void foo(){
        System.out.println("InterfaceA foo");
    }
}

class DiamondBase {
    public void foo(){
        System.out.println("Diamond base foo");
    }
}

public class DiamondExample extends DiamondBase implements interfaceA{

    public static void main(String args[]){
        new DiamondExample().foo();
    }
}

Вышеприведенный пример выводит ниже:

Diamond base foo