Классы в Java — основы основ, но их описание будет неполным без интерфейсов, с которыми они тесно связаны. Интерфейсы — более простая конструкция, но, как и в случае с классами, полное понимание интерфейсов требует опыта работы с ними. Зачем они нужны? Интерфейсы позволяют задавать требования к классам, то есть какие методы требуются от класса. Предположим, что мы хотим работать в приложении с генератором паролей. Генератор в нашем случае это обычный класс, с методом generate()
, возвращающим пароль.
var generator = new SimplePasswordGenerator();
generator.generate(); // Возвращает готовый пароль
generator.generate(); // Уже другой пароль
Теперь с помощью интерфейса опишем требования к классу генератора. Задача интерфейса — определить функционал, который затем будет реализован классами. Поэтому интерфейс содержит только сигнатуру методов без их реализации.
Создадим интерфейс PasswordGenerator
и опишем в нем два метода generate()
. Один метод будет без параметров, другой — с возможностью настройки длины пароля:
interface PasswordGenerator {
String generate();
String generate(int length);
}
Теперь интерфейс нужно реализовать. Делается это в определении класса:
// Ключевое слово implements означает, что класс реализует интерфейс PasswordGenerator
class SimplePasswordGenerator implements PasswordGenerator {
public String generate() {
// Обращаемся к методу объекта
// 16 – выбранное значение по умолчанию
return this.generate(16);
}
public String generate(int length) {
// Тут логика генерации простого пароля
}
}
Интерфейсы не ограничивают класс в его расширении. Помимо интерфейсных методов, мы можем добавить и любые другие.
Но все таки одно ограничение есть. Если класс применяет интерфейс, то он должен реализовывать все методы, указанные в интерфейсе, причем именно так, как они описаны.
В чем же суть интерфейсов? Если коротко: полиморфизм, а если точнее — полиморфизм подтипов (subtyping). Прямо сейчас он нам не очень нужен, но для общего развития попробуем ухватить его идею.
Осторожно, дальнейший текст может напугать. Если он кажется вам сложным, просто пропустите, все это мы будем повторять еще не раз.
Код, который будет использовать passwordGenerator
может выглядеть так:
class UserController {
// Здесь создаются пользователи
// Этот код вызывается где-то внутри приложения при регистрации пользователя
public void create() {
// Создаем пользователя
// И где-то тут же генерируем ему пароль
var generator = new SimplePasswordGenerator();
var password = generator.generate();
}
}
Неплохой код, но представьте, что класс может меняться без изменения кода, просто в зависимости от того, какие опции выбирает пользователь при генерации пароля. Вполне реальная ситуация. Самый простой способ реализовать такую схему — вставить условие на параметр, который вводит пользователь. В одном случае берем один класс, в другом другой.
var generator;
if (userChooseSomething) {
generator = new SimplePasswordGenerator();
} else {
generator = new SuperPasswordGenerator();
}
Такой код уже может стать проблемой, если генераторы будут постоянно добавляться. Ситуация ухудшится, когда эта конструкция начнет расползаться по разным частям проекта. Интерфейсы изящно решают такие ситуации.
Сделаем так, чтобы класс SuperPasswordGenerator()
тоже реализовывал интерфейс PasswordGenerator
:
class SuperPasswordGenerator implements PasswordGenerator {
public String generate() {
return this.generate(16);
}
public String generate(int length) {
// Тут уже другая логика генерации супер-сложного пароля
}
}
Код, который будет использовать PasswordGenerator
, меняется на такой:
class UserController {
// Интерфейс = Тип
// Вместо конкретного класса указываем интерфейс
public void create(PasswordGenerator generator) {
var password = generator.generate();
}
}
Интерфейсы в Java — это настоящие типы данных, поэтому их можно указывать в определениях методов.
В коде выше видно, что мы требуем передавать в метод create
тип PasswordGenerator
. Таким типом будет любой объект, класс которого реализует интерфейс PasswordGenerator
. Теперь мы можем передать в метод create()
любой генератор паролей, который реализует интерфейс PasswordGenerator
. В получившемся коде генератор создается не там, где используется. Он создается где-то раньше, а уже в код приходит нужная реализация.
Таким образом мы можем получить разное поведение, не меняя сам код, который использует PasswordGenerator
.
https://replit.com/@hexlet/java-oop-basics-intrfaces
Все это уже станет актуальным, как только мы дойдем до Java-коллекций.
Соглашения и правила
К интерфейсам предъявляются такие же требования как и к классам
- Один файл – один интерфейс. Имя файла и интерфейса совпадают.
- Интерфейсы начинаются с заглавной буквы.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.