Почему Java не разрешает множественное наследование, но позволяет соответствовать нескольким интерфейсам с реализациями по умолчанию - PullRequest
0 голосов
/ 03 октября 2018

Я не спрашиваю об этом -> Почему в Java нет множественного наследования, но допускается реализация нескольких интерфейсов?

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

interface TestInterface 
{ 
    // abstract method 
    public void square(int a); 

    // default method 
    default void show() 
    { 
      System.out.println("Default Method Executed"); 
    } 
} 

Ответы [ 6 ]

0 голосов
/ 03 октября 2018

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

Если вы посмотрите на другие языки, они обычно делятся на две широкие категории:

  1. Языки с множественным наследованием плюс несколько функций для устранения неоднозначности в особых случаях: виртуальное наследование [C ++]прямые вызовы всех суперконструкторов в наиболее производном классе [C ++], линеаризация суперклассов [Python], сложные правила для super [Python] и т. д.

  2. Языки с различной концепцией, обычно называемые интерфейсы , черты , mixins , modules и т. Д., Которые налагают некоторые ограничения, такие как:нет конструкторов [Java] или конструкторов с параметрами [Scala до самого недавнего времени], нет изменяемых полей [Java], особых правил для переопределения (например, миксины имеют приоритет над базовыми классами [Ruby], так что вы можете включать их, когда вам нужно множествослужебные методы) и т. д. Java стала таким языком.

Почему, просто запретив поля и конструкторы, вы решаете много вопросов, связанных сна множественное наследование?

  • В дублированных базовых классах не может быть дублированных полей.
    • Иерархия основного класса все еще линейна.
  • Вы не можете построить базовые объекты неправильно.
    • Представьте себе, если у объекта есть открытые / защищенные поля и все подклассы имеют конструкторы, задающие эти поля.Когда вы наследуете от более чем одного класса (все они получены из Object), какой из них получает для установки полей?Последний класс?Они становятся братьями и сестрами в иерархии, поэтому они ничего не знают друг о друге.Если у вас есть несколько копий объекта, чтобы избежать этого?Правильно ли будут работать все классы?
  • Помните, что поля в Java не являются виртуальными (перезаписываемыми), они просто хранят данные.
    • Вы можете создать язык, в котором поля будут вести себя как методы и могут быть переопределены (фактическое хранилище будет всегда приватным), но это будет гораздо большим изменением и, вероятно, больше не будет называться Java.
  • Интерфейсы не могут быть созданы сами по себе.
    • Вы всегда должны сочетать их с конкретным классом.Это устраняет необходимость в конструкторах и делает намерение программиста более ясным (то есть, что подразумевается под конкретным классом, а что за вспомогательный интерфейс / миксин).Это также обеспечивает четко определенное место для решения всех неясностей: конкретный класс.
0 голосов
/ 03 октября 2018

Разработчики языка уже думали об этом, так что эти вещи выполняются компилятором.Поэтому, если вы определите:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

И вы реализуете класс для обоих интерфейсов:

static class Impl implements First, Second {

}

, вы получите ошибку компиляции;и вам нужно переопределить go, чтобы не создавать двусмысленность вокруг него.

Но вы можете подумать, что вы можете обмануть компилятор, выполнив:

interface First {
    public default void go() {
    }
}

static abstract class Second {
    abstract void go();
}

static class Impl extends Second implements First {
}

Вы можете подумать, что First::go уже обеспечивает реализацию для Second::go, и это должно бытьхорошо.Об этом слишком заботятся, и поэтому не компилируются.

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

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

static class Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre extends Bug {
    static void test() {
        printIt(); // this will work just fine
    }
}

Но если мы изменим это для интерфейса (и вы можете реализовать несколько интерфейсов, в отличие от классов):

interface Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre implements Bug {
    static void test() {
        printIt(); // this will not compile
    }
}

Теперь это запрещено компилятором и JLS:

JLS 8.4.8: класс не наследует статические методы от своих суперинтерфейсов.

0 голосов
/ 03 октября 2018

default методы в интерфейсах создают проблему, которая:

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

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

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

0 голосов
/ 03 октября 2018

Я думаю, это в основном связано с «проблемой алмазов».Прямо сейчас, если вы реализуете несколько интерфейсов одним и тем же методом, компилятор заставляет вас переопределить метод, который вы хотите реализовать, потому что он не знает, какой использовать.Я предполагаю, что создатели Java хотели убрать эту проблему обратно, когда интерфейсы не могли использовать методы по умолчанию.Теперь они пришли к идее, что хорошо иметь возможность иметь методы с реализацией в интерфейсах, поскольку вы все равно можете использовать их в качестве функциональных интерфейсов в выражениях streams / lambda и использовать их методы по умолчанию при обработке.Вы не можете сделать это с классами, но проблема алмазов все еще существует там.Это мое предположение:)

0 голосов
/ 03 октября 2018

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

Например, с этими двумя интерфейсами:

public interface Foo {
    default void doThat() {
        // ...
    }
}

public interface Bar {    
    default void doThat() {
        // ...
    }       
}

Он не будет компилироваться:

public class FooBar implements Foo, Bar{
}

Вы должны определить / переопределить метод для удаления неоднозначности.
Например, вы можете делегировать реализации Bar, например:

public class FooBar implements Foo, Bar{    
    @Override
    public void doThat() {
        Bar.super.doThat();
    }    
}

, или делегировать реализации Foo, например::

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        Foo.super.doThat();
    }
}

, или по-прежнему определять другое поведение:

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        // ... 
    }
}

Это ограничение показывает, что Java не допускает множественное наследование даже для методов интерфейса по умолчанию.


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

  • переопределение / устранение неоднозначности для метода в обоихУнаследованные классы могут вызывать побочные эффекты и изменять общее поведение унаследованных классов, если они полагаются на этот метод внутри.В случае интерфейсов по умолчанию этот риск также присутствует, но он должен быть гораздо реже, поскольку методы по умолчанию не предназначены для введения сложных обработок, таких как множественные внутренние вызовы внутри класса, или для сохранения состояния (в действительности интерфейсы не могут размещать поле экземпляра).
  • как наследовать несколько полей?И даже если бы язык позволял это делать, у вас возникла бы та же проблема, что и в предыдущем случае: побочный эффект в поведении унаследованного класса: поле int foo, определенное в классе A и B, который вы хотите подклассироватьне имеет того же значения и намерения.
0 голосов
/ 03 октября 2018

Java не разрешает множественное наследование полей.Это будет трудно поддерживать в JVM, так как вы можете иметь только ссылки на начало объекта, где находится заголовок, а не произвольные области памяти.

В Oracle / Openjdk объекты имеют заголовок, за которым следуют полясамого суперкласса, затем следующего самого суперкласса и т. д. Было бы значительным изменением, чтобы поля класса могли появляться с разным смещением относительно заголовка объекта для разных подклассов.Скорее всего, ссылки на объекты должны стать ссылкой на заголовок объекта и ссылкой на поля для поддержки этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...