Интерфейсы в Java
Интерфейсы в Java, как ни странно, выполняют роль интерфейсов. То есть служат посредником между двумя структурами кода, связывая их между собой. В реальном мире есть множество аналогов интерфейса, работа которых заключается в связывании пользователя и объекта. Например, пользовательский интерфейс приложения или программы.
При нажатии любой кнопки на экране человек не особо задумывается, какой код будет выполняться и каким образом. Он просто нажимает на кнопку, а интерфейс уже реализует нужный функционал. Более ощущаемый пример — пульт от любого устройства. Неважно, как устроен этот пульт, для пользователя важно наличие кнопок управления. Нажимая на них человек уверен, что управляемое устройство — ТВ, приставки, роботы-пылесосы выполнят нужную функцию. Интерфейс в Java работает примерно также.
Создание и особенности
Создаётся интерфейс, как и любой класс:
public interface Eating {
public void eat();
}
Только вместо ключевого слова class используется interface. Область видимости интерфейса тоже можно указать, но только в том случае, если файл носит такое же имя.
В интерфейс удобно выносить некие абстрактные свойства и функции объектов. Например, метод eat(). Кушать могут все создания на земле. И при реализации отдельных видов, например, животных, можно использовать интерфейс.
Также стоит обратить внимание, что метод eat() не имеет реализации. И в этом заключается особенность интерфейса. Так как мы не знаем, как ест каждый отдельный вид животных, то и реализовывать отдельный механизм надо непосредственно при разработке функционала объекта.
public class Dog implements Eating {
}
Чтобы использовать механизм интерфейса в классе, надо после его имени указать ключевое слово implements и добавить имя. Любая среда разработки сразу же сообщит, что раз класс реализует некий интерфейс, то его методы должны быть добавлены в класс и переопределены.
public class Dog implements Eating {
public void eat() {
System.out.println(“Собака грызёт кость”);
}
public static void main(String[] args) {
Dog dog = new Dog();
Dog.eat();
}
}
После добавления метода интерфейса в представленный класс, мы может конкретизировать его реализацию. В данном случае мы выводим сообщение о том, как собаки любят есть — грызть кость. Для другого животного можно было описать другой способ потребления пищи — коты пьют молоко, мышки едят сыр, коровы щиплют траву. То есть мы абстрагировали общий для всех объектов функционал, а затем в каждом конкретном случае уточнили как именно это происходит. В этом и заключается механизм работы интерфейсов.
Стоит отметить, что в Java 8 появился новый подход к реализации методов. С помощью ключевого слова defaultможно указать метод, общий для всех и добавить его реализацию прямо в интерфейс. Например, коты, собаки и коровы ходят на 4 лапах и ногах, но едят разную пищу. Это значит, что можно вынести одинаковый функционал в отдельный метод.
public interface Walking {
public default walk() {
System.out.println(“Ходит на 4 ногах”);
}
}
Теперь при реализации этого интерфейса в классе нет необходимости указывать его точную реализацию. Она просто выполнится по умолчанию при вызове.
Теперь о наследовании. Так как Javaне поддерживает множественное наследование, интерфейсы частично могут решить эту проблему. Реализовывать интерфейсов в одном классе можно сколько угодно, указав их через запятую после слова implements.
public class Dog implements Eating, Walking, Jumping {
}
При необходимости, интерфейсы можно наследовать друг от друга, также, как и классы, с помощью ключевого слова extends. Но, в отличие от классов, через запятую можно указать несколько имён и реализовать таким образом множественное наследование.
public interface EatingMeat extends Eating {
}
Применение интерфейсов
Естественно, что на практике, в небольших проектах применение интерфейсов может быть избыточным. В больших же они как раз первые помощники, которые помогают структурировать функциональные особенности объектов и выносить их в отдельные реализации. А это упорядочивает код и упрощает его использование.
Очень часто интерфейсы используют для исполнения механизма обратного вызова или callback. Особенно в среде разработки под Андроид. Такой подход позволяет связывать между собой участки кода в разных классах и даже передавать данные.
public class DogEating {
interface CallbackDogFinishedEat{
void dogFinishedEat();
}
CallbackDogFinishedEatcallbackDogFinishedEat;
public void registerCall(CallbackDogFinishedEatcallbackDogFinishedEat) {
this.callbackDogFinishedEat = callbackDogFinishedEat;
}
void doDogEating() {
//выполнение долгой задачи, собака ест
сallbackDogFinishedEat.dogFinishedEat();
}
}
Созданный класс содержит внутренний интерфейс, который отслеживает событие, когда собака заканчивает кушать. Для этого объявлен сам интерфейс и в нём метод, объявляющий о конце процесса. Затем объявлена переменная с типом интерфейса. Метод registerCall принимает на вход наш интерфейс, который будет передан из любого другого места, и присваивает его внутреннему интерфейсу.
В конце классы вызывается долгий процесс собачьей трапезы, в конце которой вызывается метод dogFinishedEat, который характеризует конец трапезы. Теперь осталось использовать этот класс в другом классе.
public class DogIsFed implements DogEating.CallbackDogFinishedEat {
public void dogFinishedEat() {
System.out.println(“Собака накормлена”);
}
}
В этом классе мы реализуем интерфейс CallbackDogFinishedEat и его метод dogFinishedEat, который оповестит о том, что собака накормлена. Теперь нужно использовать эти классы в главном классе.
public class Main {
public static void main(String[] args) {
DogEatingdogEating = new DogEating();
DogIsFeddogIsFed = new DogIsFed;
dogEating.registerCall(dogIsFed);
dogEating.doDogEating();
}
}
В главном классе мы создаём экземпляры объектов DogEating и DogIsFed. Затем передаём DogIsFed, реализующий наш интерфейс методу, регистрирующему его. В конце мы вызываем некую длительную работу, в этом примере — процесс поедания собакой еды, в конце которого сработает наш колбек и сообщит об успешном завершении. То есть мы связали несколько разных классов общей логикой и отследили выполнение задачи из одного класса в другом.
Заключение
Понимание интерфейсов приходит не сразу. Зачем их использовать, ведь есть наследование классов? Это работает до тех пор, пока не придётся столкнуться с большим, высоконагруженным проектом, с большой иерархией классов, структур и данных. И тогда, чтобы не запутаться, нужно будет абстрагировать некоторый функционал и обобщать его с помощью интерфейсов. Такой подход позволяет экономить на количестве строк кода и времени.