Зачем нужны интерфейсы?
Зачем нужно наследование интерфейсов?
Зачем нужен полиморфизм?
Для тех, кто почитал и понял как делать интерфейсы, но не понял зачем.
Короткий ответ: интерфейсы позволяют одному классу получить возможности сразу от нескольких источников. Наследоваться в Java можно только от одного класса, а реализовать интерфейсов через ![Комикс: сын просит унаследовать привычки от папы и мамы, отец отвечает, что множественное наследование классов в Java запрещено]()
Смысл интерфейсов теперь должен быть понятен: в Java с помощью интерфейсов можно реализовать множественное наследование.
Если развивать ситуацию дальше, например: ведь у Папы и Мамы наверняка есть привычки, которые они не передали Пете, да и у Пети могут быть свои личные привычки. Как эту жизненную Санта-Барбару перенести в плоскость 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 есть интерфейсы. И для привычек мы придумаем свой интерфейс. Даже два: Выглядеть они будут так: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 вполне возможна.
Класс или интерфейс: в чем разница
Соберем различия, о которых шла речь, в одну таблицу:| Наследование класса | Реализация интерфейса | |
|---|---|---|
| Сколько штук может быть у одного класса | Ровно один родитель | Сколько угодно |
| Ключевое слово | extends |
implements |
| Поля с собственным состоянием | Есть | Нет, все объявленное становится константой public static final |
| Пример из статьи | class Петя extends Папа |
class Петя implements ПривычкиПапы, ПривычкиМамы |
Что почитать дальше
Это не единственный пример для понимания интерфейсов. Если не читали следующие статьи, то обязательно прочтите: Интерфейсы в 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 переопределять не обязательно, у них уже есть тело в самом интерфейсе.
Зачем интерфейс, если можно написать методы прямо в классе?
Ради того, чтобы остальной код не зависел от конкретного класса. Метод, который принимаетПривычкиПапы, будет работать с любым объектом, у которого эти привычки есть, и подставить туда можно и Петю, и Дедушку. Это и есть полиморфизм: интерфейс задает контракт, реализаций может быть сколько угодно, и менять их можно, не трогая вызывающий код.