Перейти к содержимому
Теория · Интерфейсы, enum и record · урок 1 из 8

Интерфейсы

Чему вы научитесь

Чему вы научитесь:

  • описывать умение интерфейсом и реализовывать его в классах через implements;
  • объяснять, когда выполняется default-метод, и выбирать между интерфейсом и абстрактным классом;
  • заводить фиксированный набор значений через enum и разбирать его в switch;
  • описывать неизменяемые данные одной строкой record и сравнивать записи через equals.

Представьте объявление о работе: «Требуется курьер. Нужно уметь доставить посылку». В объявлении не сказано, кем должен быть курьер. Работу может взять студент на велосипеде, водитель фургона и даже дрон: подходит любой, кто умеет делать то, что перечислено.

Тому, кто отправляет посылку, тоже неважно, кто за ней пришёл. Он говорит курьеру «доставь», а как доставлять, решает сам курьер.

Зачем такое нужно в программе? В прошлой теме общие кнопки классам давал родитель. Но у велосипедиста и дрона нет ничего общего, кроме умения доставлять: ни общих полей, ни общего предка. К тому же родитель у класса может быть только один. Для умения, которое бывает у совсем разных классов, в Java есть интерфейс:

JAVA
public class Main {
    public static void main(String[] args) {
        Courier c = new Cyclist();
        c.deliver("book"); // Cyclist brings book
    }
}

interface Courier { void deliver(String parcel); }

class Cyclist implements Courier {
    public void deliver(String parcel) {
        System.out.println("Cyclist brings " + parcel);
    }
}

Давайте разберём программу по шагам:

  1. interface Courier — объявление о работе. В фигурных скобках только заголовок метода deliver, а вместо тела стоит точка с запятой: интерфейс говорит, что нужно уметь, но не говорит как.
  2. class Cyclist implements Courier — велосипедист берёт эту работу. Слово implements обещает, что в классе есть каждый метод интерфейса.
  3. Внутри Cyclist метод deliver написан целиком, с телом.
  4. Courier c = new Cyclist(); заводит переменную типа Courier и кладёт в неё ссылку на объект Cyclist.
  5. c.deliver("book") выполняет метод велосипедиста и печатает Cyclist brings book.

Интерфейс и implements

Интерфейс — это тип, который перечисляет методы, но не пишет их тела. Методы без тела вы уже встречали в абстрактных классах: в интерфейсе они тоже абстрактные. Класс реализует интерфейс, когда пишет у себя каждый его метод, и объявляет об этом словом implements.

Что важно знать об интерфейсе:

  • метод без тела в интерфейсе всегда public, даже если слово не написано, поэтому и в классе его объявляют public;
  • объект интерфейса создать нельзя: на new Courier() компилятор ответит Courier is abstract; cannot be instantiated;
  • компилятор проверяет, что класс написал все методы интерфейса, и если хоть одного нет, программа не скомпилируется.

Вот что javac сообщает о классе Cyclist, в котором забыли метод deliver:

TEXT
error: Cyclist is not abstract and does not override abstract method deliver(String) in Courier

Начало сообщения, is not abstract, не случайно: абстрактному классу из прошлой темы разрешено оставить метод своим наследникам. Обычный класс обязан написать всё.

Над такими методами можно ставить @Override, как в прошлой теме. Тогда компилятор проверит, что метод с таким заголовком действительно есть в интерфейсе.

Несколько интерфейсов

Один человек может работать и курьером, и фотографом. Так и класс может реализовать сколько угодно интерфейсов: их перечисляют после implements через запятую. Дрон умеет и доставлять, и снимать сверху:

JAVA
interface Camera { String photo(); }

class Drone implements Courier, Camera {
    public void deliver(String parcel) {
        System.out.println("Drone drops " + parcel);
    }

    public String photo() {
        return "view from above";
    }
}

Родитель через extends у класса по-прежнему один, а интерфейсов — сколько нужно. Если есть и то и другое, сначала пишут extends, потом implements: class Van extends Vehicle implements Courier, Camera.

Этот вопрос часто задают на собеседовании: есть ли в Java множественное наследование? Ответ: класс наследует только один класс, но может реализовать несколько интерфейсов.

Переменная типа интерфейса

Переменная Courier c работает как универсальный пульт из прошлой темы: её тип решает, какие кнопки есть, а объект решает, что они делают. В переменную типа Courier можно положить объект любого класса, который реализует Courier:

JAVA
Courier first = new Cyclist();
Courier second = new Drone();
first.deliver("pizza");  // Cyclist brings pizza
second.deliver("pizza"); // Drone drops pizza

Разберём по строкам:

  1. В first лежит велосипедист, а в second — дрон, но тип у обеих переменных один: Courier.
  2. first.deliver("pizza") выполняет метод класса Cyclist.
  3. second.deliver("pizza") записан так же, а работу делает другой объект: выполняется метод класса Drone.

У пульта Courier одна кнопка — deliver. Поэтому second.photo() не скомпилируется, хотя в second лежит дрон: компилятор ответит cannot find symbol. Чтобы снять фото, нужна переменная типа Camera.

Здесь картинка с объявлением неточна. Настоящий работодатель может взять человека, который умеет только половину, в надежде, что тот научится. Компилятор так не делает: класс получает работу, только если умеет всё, что перечислено.

Частая ошибка

В классе забыли написать public перед методом интерфейса:

JAVA
class Cyclist implements Courier {
    void deliver(String parcel) { // ✗
        System.out.println("Cyclist brings " + parcel);
    }
}
TEXT
error: deliver(String) in Cyclist cannot implement deliver(String) in Courier

Программа не компилируется. Метод интерфейса — public, а метод без модификатора виден только классам своего пакета. Класс не может сузить доступ, который обещал интерфейс. Следующей строкой javac это поясняет: attempting to assign weaker access privileges; was public. Допишите public перед void deliver.

Главное

  • Интерфейс перечисляет методы без тела, а класс реализует его через implements и пишет каждый метод сам, с public.
  • Если класс не написал хоть один метод интерфейса, программа не скомпилируется.
  • Родитель через extends у класса один, а интерфейсов можно реализовать сколько угодно.
  • Переменная типа интерфейса принимает объект любого класса, который его реализует, и даёт вызвать только методы интерфейса.