1. Знакомство с интерфейсами

Сегодня у вас прямо день знаний. Еще одна новая и интересная тема — это интерфейсы.

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

interface Кошачьи
{
   void мурчать();
   void мяукать();
   void рычать();
}

Вот несколько полезных фактов об интерфейсах:

1. Объявление интерфейса

interface Drawable
{
   void draw();
}

interface HasValue
{
   int getValue();
}
  1. Вместо слова class пишем interface.
  2. Содержит только абстрактные методы (слово abstract писать не нужно)
  3. На самом деле у интерфейсов все методы – public
2. Наследование интерфейсов

Интерфейс может наследоваться только от интерфейсов. Зато родителей у интерфейса может быть много. Еще говорят, что в Java есть множественное наследование интерфейсов. Примеры:

interface Element extends Drawable, HasValue
{
   int getX();
   int getY();
}

3. Наследование классов от интерфейсов

Класс может наследоваться от нескольких интерфейсов (и только от одного класса). При этом используется ключевое слово implements. Пример:

abstract class ChessItem implements Drawable, HasValue
{
   private int x, y, value;
   public int getValue()
   {
      return value;
   }

   public int getX()
   {
      return x;
   }

   public  int getY()
   {
      return y;
   }
}

Класс ChessItem объявлен абстрактным: он реализовал все унаследованные методы, кроме draw. Т.е. класс ChessItem содержит один абстрактный метод — draw().

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

4. Переменные

И самое важное: в интерфейсах нельзя объявлять переменные (хотя статические можно).

А зачем же нужны интерфейсы? Когда их используют? У интерфейсов есть два сильных преимущества по сравнению с классами, которые мы рассмотрим далее.



2. Отделение «описания методов» от их реализации.

Раньше мы уже рассказывали, что если вы хотите разрешить вызывать методы своего класса из других классов, то их нужно пометить ключевым словом public. Если же хотите, чтобы какие-то методы можно было вызывать только из этого же класса, их нужно помечать ключевым словом private. Другими словами, мы делим методы класса на две категории: «для всех» и «только для своих».

С помощью интерфейсов это деление можно усилить еще больше. Мы сделаем специальный «класс для всех», и второй «класс для своих», который унаследуем от первого. Вот как это примерно будет:

Было Стало
class Student
{
   private String name;
   public Student(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return this.name;
   }

   private void setName(String name)
   {
      this.name = name;
   }
}
interface Student
{
   public String getName();
}

class StudentImpl implements Student
{
   private String name;
   public StudentImpl(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return this.name;
   }

   private void setName(String name)
   {
      this.name = name;
   }
}
public static void main(String[] args)
{
   Student student = new Student("Alibaba");
   System.out.println(student.getName());
}
public static void main(String[] args)
{
   Student student = new StudentImpl("Ali")
   System.out.println(student.getName());
}

Мы разбили наш класс на два: интерфейс и класс, унаследованный от интерфейса. И в чем тут преимущество?

Один и тот же интерфейс могут реализовывать (наследовать) различные классы. И у каждого может быть свое поведение. Так же, как ArrayList и LinkedList — это две различные реализации интерфейса List.

Таким образом, мы скрываем не только различные реализации, но и сам класс, который ее содержит (везде в коде может фигурировать только интерфейс). Это позволяет очень гибко, прямо в процессе исполнения программы, подменять одни объекты на другие, меняя поведение объекта скрытно от всех классов, которые его используют.

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


3. Множественное наследование

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

В Java пришли к компромиссу: запретили множественное наследование классов, но разрешили множественное наследование интерфейсов. Интерфейс может иметь несколько интерфейсов-родителей. Класс может иметь несколько интерфейсов-родителей и только один класс-родитель.

Почему же множественное наследование классов запретили, а интерфейсов — разрешили? Все дело в так называемом пирамидальном наследовании:

Множественное наследование

Когда класс B наследуется от класса A, он ничего не знает о классах C и D. Поэтому он использует переменные класса A так, как считает нужным. Класс C делает то же самое: использует переменные класса A, но уже другим способом. И в классе D это все выливается в конфликт.

Давайте рассмотрим такой простой пример. Допустим, у нас есть 3 класса:

class Data
{
   protected int value;
}
class XCoordinate extends Data
{
   public void setX (int x) { value = x;}
   public int getX () { return value;}
}
class YCoordinate extends Data
{
   public void setY (int y) { value = y;}
   public int getY () { return value;}
}

Класс Data хранит у себя переменную value. Его класс-наследник XCoordinate использует ее для того, чтобы хранить в ней переменную x, а класс-наследник YCoordinate использует ее для того, чтобы хранить в ней переменную y.

И это работает. По отдельности. Но вот если мы захотим унаследовать класс XYCoordinates от обоих классов, XCoordinate и YCoordinate, получим неработающий код. У этого класса будут методы его классов-предков, но работать они будут неправильно, т.к. переменная value у них одна.

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