JavaRush /Курсы /Модуль 2. Java Core /Дефолтные методы в интерфейсах

Дефолтные методы в интерфейсах

Модуль 2. Java Core
4 уровень , 3 лекция
Открыта

Каждая новая версия Java отличается от предыдущих. Вот пример изменений из материала, который мы проходили: до Java 5 в языке не было enum’ов.

Дефолтные методы в интерфейсах - 1
Так и Java 8 заметно отличается от Java 7. Игнорировать важные нововведения мы, конечно, не будем.

Раз уж в этой лекции говорим об интерфейсах, рассмотрим одно обновление — дефолтные методы в интерфейсах.

Ты уже знаешь, что интерфейс не реализует поведение. Его задача — описать, какое поведение должны иметь все объекты, которые его имплементируют.

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

Давай рассмотрим наш старый пример с машинами:


public interface Car {

   public void gas();
  
   public void brake();
}
public class Sedan implements Car {

   @Override
   public void gas() {
       System.out.println("Газ!");
   }

   @Override
   public void brake() {
       System.out.println("Тормоз!");
   }
}


public class Truck implements Car {

   @Override
   public void gas() {
       System.out.println("Газ!");
   }

   @Override
   public void brake() {
       System.out.println("Тормоз!");
   }
}


public class F1Car implements Car {
   @Override
   public void gas() {
       System.out.println("Газ!");
   }

   @Override
   public void brake() {
       System.out.println("Тормоз!");
   }
}

Какая, по-твоему, главная проблема этого кода?

Ты наверняка обратил внимание, что мы написали кучу одинакового кода! Эта проблема распространена в программировании, и нужно ее избегать.

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

Вот как это делается:


public interface Car {

   public default void gas() {
       System.out.println("Газ!");
   }

   public default void brake() {
       System.out.println("Тормоз!");
   }
}

public class Sedan implements Car {

}

public class Truck implements Car {

}

public class F1Car implements Car {

}

Теперь методы gas() и brake(), которые для всех машин были одинаковыми, вынесены в интерфейс, и повторяющийся код не нужен. При этом методы доступны в каждом из классов!


public class Main {

   public static void main(String[] args) {

       F1Car f1Car = new F1Car();
       Sedan sedan = new Sedan();
       Truck truck = new Truck();
       truck.gas();
       sedan.gas();
       f1Car.brake();
   }
}

А если есть 100 классов с методом gas(), но одинаковое поведение имеют только 99 из них? Это все портит, и дефолтный метод в этом случае не подойдет?

Конечно, нет :) Дефолтные методы интерфейсов можно переопределять.


public class UnusualCar implements Car {
   @Override
   public void gas() {
       System.out.println("Эта машина газует по-другому!");
   }

   @Override
   public void brake() {
       System.out.println("Эта машина тормозит по-другому!");
   }
}

Все остальные 99 типов машин будут реализовывать метод по умолчанию, а класс UnusualCar — исключение — не испортит общей картины и спокойно определит свое поведение.

Множественное наследование в интерфейсах

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

В других языках, например, в C++, оно наоборот есть. Без множественного наследования возникает серьезная проблема: у одного и того же объекта может быть ряд разных характеристик и «поведений».

Пример из жизни: для наших родителей мы — дети, для преподавателей — студенты, для врачей — пациенты. В жизни мы предстаем в разных ролях и, соответственно, ведем себя по-разному: с преподавателями мы явно будем разговаривать не так, как с близкими друзьями.

Попробуем переложить эту ситуацию в код. Представим, что у нас есть два класса: Пруд и Авиарий. Для пруда нужны плавающие птицы, а для авиария — летающие.

Для этого мы создали два базовых класса — FlyingBird и Waterfowl.


public class Waterfowl {
}

public class FlyingBird {
}

Соответственно, в авиарий будем отправлять тех птиц, классы которых унаследованы от FlyingBird, а в пруд — тех, кто происходит от Waterfowl.

Вроде все выглядит просто.

Но что будем делать, если надо будет куда-то определить утку?

Она и плавает, и летает. А множественного наследования у нас нет.

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

Наша утка может быть и летающей, и плавающей :) Достаточно сделать FlyingBird и Waterfowl не классами, а интерфейсами, чтобы достичь нужного результата.


public class Duck implements FlyingBird, Waterfowl {

   //методы обоих интерфейсов легко объединяются в одном классе
  
   @Override
   public void fly() {
       System.out.println("Летим!");
   }

   @Override
   public void swim() {

       System.out.println("Плывем!");
   }
}

Благодаря этому наша программа сохраняет гибкое управление классами, а в сочетании с реализацией дефолтных методов наши возможности определять поведение объектов становятся практически безграничными! :)

4
Задача
Java Core, 4 уровень, 3 лекция
Недоступна
Набираем код Ӏ Java Core: 4 уровень, 3 лекция
Внимание! Объявляется набор кода на JavaRush. Чтобы поучаствовать, включите режим повышенной внимательности, немного разомните пальцы, затем — расслабьте их и… набирайте код в соответствующем окошке. На самом деле это довольно полезное занятие, а не пустая трата времени, как может показаться.
Комментарии (9)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Max Уровень 79
8 февраля 2025
Вроде раньше мы это уже обсуждали, ну да ладно, повторение как говориться :)
Evgenii Vasiukov Уровень 3
14 октября 2024
Было бы неплохо добавить информации про возможный конфликт дефолтных методов. Если класс реализует два или более интерфейсов, которые содержат дефолтные методы с одинаковой сигнатурой, Java потребует от вас явно разрешить конфликт. Это можно сделать, переопределив метод в классе:

public interface InterfaceA {
    default void myMethod() {
        System.out.println("Дефолтный метод из InterfaceA");
    }
}

public interface InterfaceB {
    default void myMethod() {
        System.out.println("Дефолтный метод из InterfaceB");
    }
}

public class MyClass implements InterfaceA, InterfaceB {
    @Override
    public void myMethod() {
        // Явно разрешаем конфликт между методами из InterfaceA и InterfaceB
        InterfaceA.super.myMethod(); // Вызываем реализацию из InterfaceA
        // или
        InterfaceB.super.myMethod(); // Вызываем реализацию из InterfaceB
    }
}

Илья Уровень 92 Expert
16 июня 2022
Создаём два класса или всё-таки два интерфейса, потому что потом мы их имплементим? public class Waterfowl { } public class FlyingBird { } public class Duck implements FlyingBird, Waterfowl {
Дмитрий Уровень 43
18 июня 2022
там же ниже написано "Достаточно сделать FlyingBird и Waterfowl не классами, а интерфейсами, чтобы достичь нужного результата."
Виталий Уровень 20
22 февраля 2022

Множественное наследование в интерфейсах Как ты уже знаешь, в Java нет множественного наследования.
Нужно поправить
Andrei Уровень 34
29 июня 2022
С тех пор не исправили
Abdulla Уровень 41
2 октября 2022
Мы не знаем, что это такое. Если бы мы знали, что это такое, но мы не знаем, что это такое.
Татьяна Уровень 48 Expert
28 апреля 2023
всё еще не исправили...
Руслан Уровень 48
15 сентября 2025
до сих пор не исправили😂😂,хотя как бы понятно все написано