До Java 1.7 Java не поддерживала множественное наследование. Начиная с Java 8, мы можем реализовать концепцию множественного наследования с помощью методов по умолчанию, не вдаваясь в проблему алмаза.
1. Что такое множественное наследование?
При множественном наследовании дочерний класс может наследовать поведение более чем от одного родительского класса. Обратите внимание, что класс Java может реализовывать несколько интерфейсов, но интерфейс не определяет конкретное поведение, а интерфейсы используются только для определения контрактов. Таким образом, реализация нескольких интерфейсов не добавляет поведение к классу, поэтому это не множественное наследование.
На следующей диаграмме класс D расширяет классы A и B. Таким образом, D может наследовать не закрытые члены обоих классов. Но в Java мы не можем использовать ключевое слово extends с двумя классами. Поэтому этот вид множественного наследования невозможен в Java.

2. Что такое методы по умолчанию?
Если вы занимаетесь программированием на Java достаточно долго, вы можете понять, насколько болезненным может быть добавление нового метода к существующему интерфейсу. Мы должны реализовать этот новый метод во всех классах, которые реализуют интерфейс. Это действительно сложная работа, которая часто нарушает код и дизайн. Что ж, Java 8 привнесла методы по умолчанию, чтобы помочь решить именно эту проблему.
Методы по умолчанию позволяют нам добавлять новые функциональные возможности в интерфейсы и обеспечивать обратную совместимость для существующих классов, реализующих этот интерфейс.
Как следует из их названия, методы по умолчанию в интерфейсах — это методы, которые будут вызываться по умолчанию — если не будут переопределены в классах реализации. Давайте разберемся на примере.
Интерфейс Moveable — это существующий интерфейс, и мы хотим добавить новый метод moveFast(). Если мы добавим метод moveFast(), используя старую технику, то все классы, реализующие Moveable, также будут изменены. Итак, давайте добавим метод moveFast() в качестве метода по умолчанию.
public interface Moveable {default void moveFast() {System.out.println("I am moving fast");}}
В этом случае все классы, реализующие интерфейс Moveable, не нуждаются в изменении самих себя(пока какой-либо класс специально не захочет переопределить метод moveFast() для добавления собственной логики). Все классы могут напрямую вызывать метод instance.moveFast().
class Animal implements Moveable { }Animal tiger = new Animal();//Call default method using instance referencetiger.moveFast();
Мы можем добавить в интерфейс столько методов по умолчанию, сколько захотим.
3. Как достигается множественное наследование с помощью методов по умолчанию?
Начиная с Java 8, интерфейсы не только определяют контракты, но и содержат поведение с использованием методов по умолчанию. Таким образом, если класс реализует два интерфейса и оба определяют методы по умолчанию, то класс по сути наследует поведение от двух родителей, что является множественным наследованием.
Например, в приведенном ниже коде класс Animal не определяет никакого собственного поведения; вместо этого он наследует поведение от родительских интерфейсов.
interface Moveable {default void moveFast() {System.out.println("I am moving fast");}}interface Crawlable {default void crawl(){System.out.println("I am crawling");}}public class Animal implements Moveable, Crawlable { }
Мы можем проверить поведение следующим образом:
Animal self = new Animal();self.moveFast();self.crawl();
4. Возможные конфликты
В приведенном выше примере у нас есть два разных интерфейса и два разных метода, поэтому конфликта нет. Что если оба интерфейса решат определить новый метод с тем же именем? Ну, они могут определить без проблем. Но какой метод будет вызван, когда экземпляр Animal вызовет метод по имени.
interface Moveable {default void run() {System.out.println("I am moving fast");}}interface Crawlable {default void run(){System.out.println("I am crawling");}}public class Animal implements Moveable, Crawlable { }
Что же произойдет, когда мы вызовем метод animal.run()?
Animal animal = new Animal();animal.run();
Это конфликтная ситуация. Чтобы разрешить указанный выше конфликт, вызывающий класс должен решить, какой метод run() он хочет вызвать, а затем вызвать его, используя ссылку интерфейса.
Moveable.super.run(); //Call Moveable's run() method//orCrawlable.super.run(); //Call Crawlable's run() method
Это все, что вам следует знать о функции множественного наследования в Java 8 с использованием методов по умолчанию.