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

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

Открыта

Каждая новая версия 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. Чтобы поучаствовать, включите режим повышенной внимательности, немного разомните пальцы, затем — расслабьте их и… набирайте код в соответствующем окошке. На самом деле это довольно полезное занятие, а не пустая трата времени, как может показаться.
Комментарии (10)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Валерка Ферум Software Developer
5 июня, 14:35
Абстрактный класс сейчас офигел от такого поворота событий
Max
Уровень 93
8 февраля 2025, 18:11
Вроде раньше мы это уже обсуждали, ну да ладно, повторение как говориться :)
Evgenii VasiukovQA Automation Engineer в Deutsche Telekom IT
14 октября 2024, 12:48
Было бы неплохо добавить информации про возможный конфликт дефолтных методов. Если класс реализует два или более интерфейсов, которые содержат дефолтные методы с одинаковой сигнатурой, 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, 16:25
Создаём два класса или всё-таки два интерфейса, потому что потом мы их имплементим? public class Waterfowl { } public class FlyingBird { } public class Duck implements FlyingBird, Waterfowl {
Дмитрий
Уровень 43
18 июня 2022, 07:13
там же ниже написано "Достаточно сделать FlyingBird и Waterfowl не классами, а интерфейсами, чтобы достичь нужного результата."
ВиталийРаботает в ИП
22 февраля 2022, 11:27
Множественное наследование в интерфейсах Как ты уже знаешь, в Java нет множественного наследования.
Нужно поправить
Andrei
Уровень 34
29 июня 2022, 15:41
С тех пор не исправили
Abdulla
Уровень 41
2 октября 2022, 06:53
Мы не знаем, что это такое. Если бы мы знали, что это такое, но мы не знаем, что это такое.
Татьяна
Уровень 48
Expert
28 апреля 2023, 18:34
всё еще не исправили...
Руслан
Уровень 48
15 сентября 2025, 10:09
до сих пор не исправили😂😂,хотя как бы понятно все написано