Зачем нужны интерфейсы? Зачем нужно наследование интерфейсов? Зачем нужен полиморфизм? Для тех, кто почитал и понял как делать интерфейсы, но не понял зачем. Короткий ответ: интерфейсы позволяют одному классу получить возможности сразу от нескольких источников. Наследоваться в Java можно только от одного класса, а реализовать интерфейсов через implements класс может сколько угодно. В статье это показано на семье Ивановых: Петя не может унаследовать привычки и от Папы, и от Мамы через extends, зато легко получает их через два интерфейса.

Кратко

  • Наследоваться в Java можно только от одного класса: extends Папа, Мама не скомпилируется.
  • Интерфейсов же класс может реализовать сколько угодно, перечислив их через запятую после implements.
  • Интерфейс описывает, что объект умеет делать, а как именно, решает класс, который его реализует.
  • Поэтому привычки Папы и Мамы выносят в два интерфейса, а Петя реализует оба сразу.
  • Реализовать интерфейс значит написать тело для всех его методов, иначе класс придется объявить абстрактным.
  • Множественное наследование классов запрещено из-за конфликтов: если два родителя дадут одинаковый метод или поле, компилятор не поймет, чей брать.

Дисклеймер: В этой статье и последующих, нейминги классов и методов будут приведены на русском языке.

Задача: семья Ивановых

Когда речь заходит о принципах объектно-ориентированного программирования (ООП): полиморфизм, наследование и инкапсуляция, полезно приводить аналогии из реального мира. Большой плюс ООП в том, что мы в программе можем смоделировать часть реальной вселенной. Смоделируем семью Ивановых: Папа, Мама и мальчик Петя. От Папы Петя унаследовал привычку прихлюпывать когда пьет чай, а от Мамы он унаследовал привычку поджимать губы во время чтения. Если попытаться реализовать эту ситуацию в программу, то у нас получится три класса:
class Папа
class Мама
class Петя
У Папы и Мамы есть привычки, которые нужно передать Пете. Привычки это какие-то действия, так что лучше всего реализовать их в программном мире как методы: Сначала Папа:
class Папа {
    public void прихлюпывать() {
        System.out.println("Хлюп");
    }
}
Теперь Мама:
class Мама {
    public void поджимать() {
        System.out.println("Поджать губки");
    }
}

Почему нельзя унаследоваться от двух классов

Если речь идет о наследовании, то логично написать код так:
class Петя extends Папа, Мама {
    @Override
    public void прихлюпывать() {
        System.out.println("Хлюп");
    }

    @Override
    public void поджимать() {
        System.out.println("Поджать губки");
    }
}
То есть унаследовать Петю от Папы и Мамы одновременно. Если так написать, то компилятор будет ругаться, потому что в Java нельзя реализовать множественное наследование классов. К слову, в C++ можно, а вот в Java нельзя, потому что с множественным наследованием могут возникнуть большие проблемы: подробно об этом пишет руководство Oracle. Комикс: сын просит унаследовать привычки от папы и мамы, отец отвечает, что множественное наследование классов в Java запрещено

Как это решают интерфейсы

Чтобы обойти это «нельзя», в Java есть интерфейсы. И для привычек мы придумаем свой интерфейс. Даже два: Выглядеть они будут так:
public interface ПривычкиПапы {
    public void прихлюпывать();
}
public interface ПривычкиМамы {
    public void поджимать();
}
В интерфейсе мы только описали привычки, но не описали что они конкретно делают, потому что конкретную реализацию мы пропишем в классах. Небольшая оговорка про версии. Так было до Java 8, когда интерфейс мог содержать только объявления методов. Сейчас в нем можно писать и готовую реализацию: для этого есть методы default и static, а с Java 9 еще и private методы для внутренних нужд самого интерфейса. Чего у интерфейса по-прежнему нет, так это полей с состоянием: все объявленное в нем автоматически становится константой public static final. Сначала отдадим Папе и Маме их законные привычки.
class Папа implements ПривычкиПапы {

    @Override
    public void прихлюпывать() {
        System.out.println("Хлюп");
    }
}

class Мама implements ПривычкиМамы {

    @Override
    public void поджимать() {
        System.out.println("Поджать губки");
    }
}
И теперь, абсолютно легально можно передать Пете привычки от Папы и Мамы одновременно
class Петя implements ПривычкиПапы, ПривычкиМамы {

    @Override
    public void прихлюпывать() {
        System.out.println("Хлюп");
    }

    @Override
    public void поджимать() {
        System.out.println("Поджать губки");
    }
}
То есть множественная реализация (чаще говорят имплементация) в Java вполне возможна. Мем с бегущим мальчиком и подписью «Я могу прихлюпывать и поджимать губки»Смысл интерфейсов теперь должен быть понятен: в Java с помощью интерфейсов можно реализовать множественное наследование.

Класс или интерфейс: в чем разница

Соберем различия, о которых шла речь, в одну таблицу:
Наследование класса Реализация интерфейса
Сколько штук может быть у одного класса Ровно один родитель Сколько угодно
Ключевое слово extends implements
Поля с собственным состоянием Есть Нет, все объявленное становится константой public static final
Пример из статьи class Петя extends Папа class Петя implements ПривычкиПапы, ПривычкиМамы
Если развивать ситуацию дальше, например: ведь у Папы и Мамы наверняка есть привычки, которые они не передали Пете, да и у Пети могут быть свои личные привычки. Как эту жизненную Санта-Барбару перенести в плоскость Java вы узнаете в следующих сериях.

Что почитать дальше

Это не единственный пример для понимания интерфейсов. Если не читали следующие статьи, то обязательно прочтите: Интерфейсы в Java (если не открыто, можно выйти из профиля или прочитать в режиме инкогнито) Для чего в Java нужны интерфейсы - тут реализуйте все примеры из статьи и поизменяйте методы и в интерфейсах, и в классах: наименования методов, сигнатуры (то что метод принимает на вход), типы вывода методов. Разберитесь самостоятельно:
  • с разницей при имплементации интерфейса с классом и абстрактным классом;
  • с дефолтными методами.

Вопросы и ответы

Почему в Java нельзя наследоваться от двух классов?

Из-за конфликтов, которые невозможно разрешить автоматически. Если оба родителя дают метод с одинаковой сигнатурой или поле с одинаковым именем, компилятор не может решить, чью версию брать. В C++ множественное наследование есть, и там эту проблему разбирают вручную, а создатели Java решили ее просто не создавать: об этом же говорит и руководство Oracle. Наследование состояния осталось одиночным, а множественность вынесли в интерфейсы.

Чем implements отличается от extends?

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

Сколько интерфейсов может реализовать один класс?

Ограничения нет. Их перечисляют через запятую: class Петя implements ПривычкиПапы, ПривычкиМамы, ПривычкиБабушки. На практике длинный список это признак того, что класс взял на себя слишком много, но формально язык этому не мешает.

Можно ли в интерфейсе написать реализацию метода?

Сейчас да. До Java 8 интерфейс мог содержать только объявления методов без тела, и примеры в статье написаны именно так. С Java 8 появились методы default с готовой реализацией и static методы, а с Java 9 еще и private методы для внутренних нужд интерфейса. Чего у интерфейса по-прежнему нет, так это обычных полей: все, что в нем объявлено, автоматически становится константой public static final.

Обязательно ли реализовывать все методы интерфейса?

Да, если класс обычный: не реализовали хотя бы один метод, и код не скомпилируется. Выход один, объявить класс abstract и оставить нереализованные методы наследникам. Методы с модификатором default переопределять не обязательно, у них уже есть тело в самом интерфейсе.

Зачем интерфейс, если можно написать методы прямо в классе?

Ради того, чтобы остальной код не зависел от конкретного класса. Метод, который принимает ПривычкиПапы, будет работать с любым объектом, у которого эти привычки есть, и подставить туда можно и Петю, и Дедушку. Это и есть полиморфизм: интерфейс задает контракт, реализаций может быть сколько угодно, и менять их можно, не трогая вызывающий код.

Читайте также