Дефолтные (default) методы в интерфейсах появились в Java 8: это методы с готовой реализацией прямо внутри интерфейса, объявленные ключевым словом
default. Они избавляют от дублирования одинакового кода во всех классах, реализующих интерфейс: общая реализация пишется один раз в интерфейсе и доступна всем классам, а при необходимости ее можно переопределить.
Каждая новая версия Java отличается от предыдущих. Вот пример изменений из материала, который мы проходили: до Java 5 в языке не было
enum’ов.
Кратко
- До Java 8 интерфейс мог только описывать методы, но не реализовывать их.
- С Java 8 в интерфейсе можно писать метод с готовой реализацией через ключевое слово
default.
- Default-метод избавляет от дублирования одинакового кода во всех классах, реализующих интерфейс.
- Такой метод доступен во всех классах автоматически, реализовывать его в классе не обязательно.
- При необходимости default-метод можно переопределить (
@Override) в конкретном классе.
- Класс может реализовать несколько интерфейсов сразу, хотя множественного наследования классов в Java нет.
Так и Java 8 заметно отличается от Java 7. Большинство наших лекций написаны на 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();
}
}
Коротко разницу между обычным и default-методом интерфейса можно свести в таблицу:
| Критерий | Обычный метод интерфейса | Default-метод |
| Тело (реализация) | нет | есть, прямо в интерфейсе |
| Ключевое слово | отсутствует | default |
| Нужно реализовать в классе | обязательно | не обязательно |
| Переопределение в классе | класс пишет реализацию сам | можно переопределить при необходимости |
| Появился | был всегда | с Java 8 |
А если есть 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("Плывем!");
}
}
Благодаря этому наша программа сохраняет гибкое управление классами, а в сочетании с реализацией дефолтных методов наши возможности определять поведение объектов становятся практически безграничными! :) Больше о default-методах можно почитать в
документации Oracle (Default Methods).
Читайте также
Вопросы и ответы
Что такое дефолтные методы в интерфейсах?
Это методы с готовой реализацией внутри интерфейса, появившиеся в Java 8. Они объявляются ключевым словом
default и доступны всем классам, реализующим интерфейс.
Зачем нужны default-методы?
Чтобы не дублировать одинаковый код во всех классах, реализующих интерфейс. Общую реализацию выносят в интерфейс один раз, и она доступна всем классам.
Как объявить default-метод?
Написать метод в интерфейсе с ключевым словом
default и телом:
public default void gas() { ... }. В классах его реализовывать заново не нужно.
Можно ли переопределить default-метод?
Да. Если конкретному классу нужно другое поведение, он переопределяет метод через
@Override, а остальные классы используют реализацию по умолчанию.
Чем default-метод отличается от обычного метода интерфейса?
Обычный метод интерфейса не имеет тела, и его обязан реализовать каждый класс. Default-метод уже содержит реализацию, поэтому класс может ее не писать.
Можно ли реализовать несколько интерфейсов сразу?
Да. Множественного наследования классов в Java нет, но класс может реализовать сколько угодно интерфейсов, объединяя их поведение (например,
Duck implements FlyingBird, Waterfowl).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ