1. Способности

Чтобы лучше понять преимущества интерфейсов и места их использования, нужно поговорить о более абстрактных вещах.

Класс — это, чаще всего модель какого-то конкретного объекта. Интерфейс же больше соответствует не объектам, а их способностям или ролям.

Суть интерфейсов

Например, такие вещи, как машина, велосипед, мотоцикл и колесо лучше всего представить в виде классов и объектов. А такие их способности, как «могу ездить», «могу перевозить людей», «могу стоять», лучше представить в виде интерфейсов. Вот несколько примеров:

Код Описание
interface Movable
{
   void move(String newAddress);
}
соответствует способности передвигаться
interface Driveable
{
   void drive(Driver driver);
}
соответствует способности управляться водителем
interface Transport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
соответствует способности перевозить грузы
class Wheel implements Movable
{
   ...
}
класс Wheel (колесо) обладает способностью передвигаться
class Car implements Movable, Driveable, Transport
{
   ...
}
класс Car (машина) обладает способностью передвигаться, управляться человеком и перевозить грузы
class Skateboard implements Movable, Driveable
{
   ...
}
класс Skateboard (скейтборд) обладает способностью передвигаться и управляться человеком


2. Роли

Интерфейсы сильно упрощают жизнь программиста. Очень часто в программе есть тысячи объектов, сотни классов и всего пара десятков интерфейсовролей. Ролей мало, а их комбинаций — классов — очень много.

Весь смысл в том, что вам не нужно писать код для взаимодействия всех классов со всеми. Вам достаточно взаимодействовать с их ролями (интерфейсами).

Представьте, что вы дрессируете домашних животных, каждое из которых может иметь несколько разных способностей. И вы поспорили с соседом, чьи животные громче кричат. Вы просто берете всех, у которых есть способность «голос», и командуйте им: Голос!.

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

Код Описание
interface Звучание
{
   void голос();
}
Способность Звучание. Понимает команду голос — имеет соответствующий метод.
class Кот implements Звучание
{
   void голос()
   {
      println("МЯУ");
   }
}

class Собака implements Звучание
{
   void голос()
   {
      println("ГАВ");
   }
}

class Рыбка
{
   ...
}
Животные, у которых есть эта особенность.

Для удобства мы дали классам имена на русском. Такое допускается в Java, но крайне нежелательно.













Наша Рыбка не обладает способностью говорить (не реализует интерфейс Звучание).

public static void main(String[] args)
{
   // добавляем всех животных в список
   ArrayList pets = new ArrayList();
   pets.add(new Кот());
   pets.add(new Собака());
   pets.add(new Рыбка());

   // орать, если есть такая способность
   for(Object pet: pets)
   {
      if (pet instanceof Звучание)
      {
         Звучание крикун = (Звучание) pet;
         крикун.голос();
      }
   }
}
И как дать им команду?

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

А в совокупности с полиморфизмом — это вообще бомба.



3. default реализация в методах интерфейсов

Абстрактные классы могут иметь переменные и реализацию методов, но для них множественное наследование запрещено. Интерфейсы не могут иметь переменные и реализации методов, и для них множественное наследование разрешено.

Можно эту ситуацию выразить в таблице:

Способность Абстрактные классы Интерфейсы
Переменные
Реализация методов
Множественное наследование

И вот некоторым программистам очень хотелось, чтобы у интерфейсов была возможность иметь реализацию методов. Иметь возможность добавить реализацию метода — это же не значит, что ее всегда обязательно будут добавлять. Хочешь добавляй, хочешь — нет.

К тому же, проблемы с множественным наследованием в первую очередь из-за переменных. В общем, решили и сделали. Начиная с JDK 8, в Java появилась возможность добавлять в интерфейсы методы с реализацией.

Новая актуальная таблица (для JDK 8 и выше):

Способность Абстрактные классы Интерфейсы
Переменные
Реализация методов
Множественное наследование

Теперь и в абстрактных классах, и в интерфейсах можно объявлять как методы с реализацией, так и без нее. И это отличная новость!

В абстрактных классах перед методами без реализации нужно писать ключевое слово abstract, а перед методами с реализацией ничего добавлять не нужно. В интерфейсах же все наоборот. Если у метода нет реализации, ничего добавлять не нужно, если же реализация есть, нужно добавить ключевое слово default.

Для простоты приведем эту информацию в виде небольшой таблички:

Способность Абстрактные классы Интерфейсы
Методы без реализации abstract
Методы с реализацией default

Проблема

С помощью интерфейсов с методами можно значительно упростить иерархии многих классов. Например, абстрактные классы InputStream и OutputStream можно объявить интерфейсами! Это позволит использовать их гораздо чаще и гораздо удобнее.

Но в мире уже написаны десятки миллионов (миллиарды?) Java-классов. И если начать менять стандартные библиотеки, то есть риск, что что-то может поломаться. Например, всё! 😛

Чтобы случайно не сломать работающие программы и библиотеки, было решено, что реализация методов в интерфейсах получит самый низкий приоритет при наследовании.

Например, если от интерфейса с методом унаследовать второй интерфейс и в нем объявить этот же метод, но без реализации, то реализация из первого метода до класса не дойдет. Пример:

interface Pet
{
   default void meow()
   {
      System.out.println("Мяу");
   }
}

interface Cat extends Pet
{
   void meow(); // тут мы перекрыли дефолтную реализацию ее отсутствием
}

class Barsik implements Cat
{
}

Код не скомпилируется, т.к. в классе Barsik не реализован метод meow().