default-методы
В прошлом уроке класс, который реализует интерфейс, писал каждый его метод сам. Из-за этого правила интерфейс трудно менять.
Вернёмся к объявлению о работе. Допустим, работодатель дописал в него новую обязанность: после доставки отчитаться. Все курьеры, которые уже взяли работу, разом нарушают договор, ведь этого они не умеют. В Java то же самое: добавьте в Courier новый метод без тела, и каждый класс, который реализует Courier, перестанет компилироваться.
Работодатель поступает умнее и прикладывает к новой обязанности готовую инструкцию: «Если у вас нет своего способа, напишите в чат „Delivered“». Кто умеет отчитываться по-своему, так и делает, а остальные действуют по инструкции. В Java такая инструкция — default-метод. Чтобы следить только за отчётом, оставим в Courier один метод — report:
public class Main {
public static void main(String[] args) {
Courier c = new Cyclist();
c.report(); // Delivered
}
}
interface Courier {
default void report() {
System.out.println("Delivered");
}
}
class Cyclist implements Courier {}Давайте разберём программу по шагам:
- В интерфейсе
Courierметодreportнаписан с телом, а перед ним стоит словоdefault. - Класс
CyclistреализуетCourier, но своегоreportу него нет, и компилятор не возражает. c.report()выполняет тело из интерфейса и печатаетDelivered.
Метод с телом в интерфейсе
default-метод — метод интерфейса с телом и словом default перед ним. Такие методы появились в Java 8.
Что важно знать о default-методах:
- класс получает
default-метод готовым и не обязан писать его сам; default-метод, как и метод без тела,public;- класс может написать свою версию: тогда он переопределяет
default-метод, как метод родителя в прошлой теме.
На собеседовании часто спрашивают, зачем появились default-методы. Ответ: чтобы добавлять в интерфейс новые методы и не ломать классы, которые его уже реализуют.
Какой метод выполнится
Правило одно. При вызове Java сначала ищет метод в классе объекта, а потом у его родителей. Только если никто из них не написал свой метод, выполняется default-метод интерфейса. Курьер, у которого есть свой способ отчитаться, инструкцию не читает.
В виджете два курьера: у Cyclist нет своего report, а у Drone есть. Нажимайте «Следующий шаг» и следите, чья рамка ложится сверху при каждом вызове.
1public class Main {2 public static void main(String[] args) {3 Courier a = new Cyclist();4 Courier b = new Drone();5 a.report();6 b.report();7 }8}9 10interface Courier {11 default void report() {12 System.out.println("Delivered");13 }14}15 16class Cyclist implements Courier {}17 18class Drone implements Courier {19 @Override20 public void report() {21 System.out.println("Drone landed");22 }23}
a ведёт к объекту Cyclist, а в классе Cyclist своего report нет.
Оба вызова записаны одинаково, через переменную типа Courier. Какой report выполнить, решил объект: для Cyclist нашлась только инструкция из интерфейса, а Drone отчитался по-своему. Это тот же полиморфизм, что и с универсальным пультом.
Здесь картинка с инструкцией неточна. На бумажной инструкции курьер может отмечать, сколько отчётов он уже отправил. У default-метода такой бумажки нет: интерфейс не хранит данных объекта. Об этом — следующий раздел.
Абстрактный класс или интерфейс
Теперь методы с телом бывают и у интерфейса, и у абстрактного класса. Чем же они различаются? Это один из самых частых вопросов на собеседовании по Java.
| Абстрактный класс | Интерфейс | |
|---|---|---|
| Поля объекта | есть, у каждого объекта свои значения | нет, поля интерфейса — общие константы |
| Конструктор | есть | нет |
| Сколько может взять класс | одного родителя через extends | сколько угодно через implements |
За таблицей стоит разный смысл:
- абстрактный класс отвечает на вопрос «что это?»: велосипедист и водитель фургона — сотрудники, и у каждого есть имя и число доставок;
- интерфейс отвечает на вопрос «что умеет?»: доставлять умеет и сотрудник, и дрон.
Отсюда правило выбора. Если родственным классам нужны общие поля и конструктор, берите абстрактный класс. Если умение получат совсем разные классы, берите интерфейс. Класс может сочетать оба: class Van extends Vehicle implements Courier.
Частая ошибка
Число отчётов попытались хранить в поле интерфейса:
interface Courier {
int reports = 0;
default void report() {
reports++; // ✗
System.out.println("Delivered");
}
}error: cannot assign a value to static final variable reportsПрограмма не компилируется. Поле в интерфейсе — константа: слова static и final компилятор добавил сам, и сообщение называет оба. Хранить данные объекта, например число его отчётов, интерфейс не может. Такое поле объявляют в классе, а если оно нужно нескольким родственным классам — в их общем абстрактном родителе.
Главное
default-метод — метод интерфейса с телом: класс получает его готовым и может переопределить.- При вызове Java сначала ищет метод в классе объекта и его родителях, и
default-метод выполняется, только если там своего нет. - У абстрактного класса есть поля объекта и конструктор, а поля интерфейса — общие константы; зато интерфейсов класс может реализовать сколько угодно.
- Абстрактный класс описывает, что это за объект, а интерфейс — что он умеет.