Перейти к содержанию

Интерфейсы в Java

В этой статье поговорим об интерфейсах в Java и увидим, как Java использует их для реализации полиморфизма и множественного наследования.

Что такое интерфейсы в Java

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

Посмотрим на простой пример интерфейса в Java:

public interface Electronic {

    // Константа
    String LED = "LED";

    // Абстрактный метод
    int getElectricityUse();

    // Static method
    static boolean isEnergyEfficient(String electtronicType) {
        if (electtronicType.equals(LED)) {
            return true;
        }
        return false;
    }

    //Default method
    default void printDescription() {
        System.out.println("Описание Electronic");
    }
}

Можно реализовать интерфейс в классе Java, используя ключевое слово Implements.

Создадим класс Computer, который реализует только что созданный интерфейс Electronic:

public class Computer implements Electronic {

    @Override
    public int getElectricityUse() {
        return 1000;
    }
}

Правила создания интерфейсов

В интерфейсе разрешено использовать:

Необходимо помнить, что:

  • мы не можем создавать интерфейсы напрямую;
  • интерфейс может быть пустым, без методов или переменных;
  • мы не можем использовать слово final в определении интерфейса, так как это приведет к ошибке компилятора;
  • все объявления интерфейсов должны иметь модификатор доступа public или default; модификатор abstract будет автоматически добавлен компилятором;
  • метод интерфейса не может быть protected или final;
  • вплоть до Java 9 методы интерфейса не могли быть private; однако в Java 9 появилась возможность определять методы private в интерфейсах;
  • переменные интерфейса являются public, static и final по определению; не разрешено изменять их видимость.

Чего можно достичь, используя интерфейсы

Поведенческая функциональность

Мы используем интерфейсы для добавления определенных поведенческих функций, которые могут использоваться несвязанными классами. Например, Comparable, Comparator и Cloneable – это интерфейсы Java, которые могут быть реализованы несвязанными классами. Ниже приведен пример интерфейса Comparator, который используется для сравнения двух экземпляров класса Employee:

public class Employee {

    private double salary;

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

public class EmployeeSalaryComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee employeeA, Employee employeeB) {
        if (employeeA.getSalary() < employeeB.getSalary()) {
            return -1;
        } else if (employeeA.getSalary() > employeeB.getSalary()) { 
            return 1;
        } else {
            return 0;
        }
    }
}

Для получения дополнительной информации смотрите руководство по Comparator и Comparable в Java.

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

Классы Java поддерживают единственное наследование. Однако, используя интерфейсы, можно реализовать множественное наследование.

Например, в приведенном ниже примере мы видим, что класс Car реализует интерфейсы Fly и Transform. Таким образом, он наследует методы fly и transform:

public interface Transform {
    void transform();
}

public interface Fly {
    void fly();
}

public class Car implements Fly, Transform {

    @Override
    public void fly() {
        System.out.println("Я могу летать!");
    }

    @Override
    public void transform() {
        System.out.println("Я могу трансформироваться!");
    }
}

Полиморфизм

Начнем с вопроса: что такое полиморфизм? Это способность объекта принимать различные формы во время выполнения. Чтобы быть более конкретным, это выполнение метода переопределения, связанного с определенным типом объекта во время выполнения.

В Java можно добиться полиморфизма с помощью интерфейсов. Например, интерфейс Shape может принимать разные формы – это может быть круг или квадрат.

Начнем с определения интерфейса Shape:

public interface Shape {
    String name();
}

Теперь создадим класс Circle:

public class Circle implements Shape {

    @Override
    public String name() {
        return "Круг";
    }
}

А также класс Square:

public class Square implements Shape {

    @Override
    public String name() {
        return "Квадрат";
    }
}

Наконец, пришло время увидеть полиморфизм в действии, используя интерфейс Shape и его реализации. Давайте создадим несколько объектов Shape, добавим их в список и, наконец, напечатаем их имена в цикле:

List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();

shapes.add(circleShape);
shapes.add(squareShape);

for (Shape shape : shapes) {
    System.out.println(shape.name());
}

Методы по умолчанию в интерфейсах

Традиционные интерфейсы в Java 7 и более ранних версиях не обеспечивают обратной совместимости.

Это означает, что если у вас есть устаревший код, написанный на Java 7 или более ранней версии, и вы решили добавить абстрактный метод к существующему интерфейсу, то все классы, реализующие этот интерфейс, должны переопределить новый абстрактный метод. В противном случае код сломается.

Java 8 решила эту проблему, представив метод по умолчанию, который является необязательным и может быть реализован на уровне интерфейса.

Правила наследования интерфейса

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

Интерфейс, расширяющий другой интерфейс

Если интерфейс расширяет другой интерфейс, он наследует все абстрактные методы этого интерфейса. Начнем с создания двух интерфейсов HasColor и Shape:

public interface HasColor {
    String getColor();
}

public interface Box extends HasColor {
    int getHeight()
}

В приведенном выше примере Box наследуется от HasColor с помощью ключевого слова extends. Таким образом, интерфейс Box наследует getColor. В результате интерфейс Box теперь имеет два метода: getColor и getHeight.

Абстрактный класс, реализующий интерфейс

Если абстрактный класс реализует интерфейс, он наследует все его абстрактные методы и методы по умолчанию. Рассмотрим интерфейс Transform и реализующий его абстрактный класс Vehicle:

public interface Transform {
    
    void transform();
    default void printSpecs(){
        System.out.println("Спецификация трансформации");
    }
}

public abstract class Vehicle implements Transform {}

В этом примере класс Vehicle наследует два метода: абстрактный метод transform и метод по умолчанию printSpecs.

Функциональные интерфейсы

С самого начала у Java было много функциональных интерфейсов, таких как Comparable (начиная с Java 1.2) и Runnable (начиная с Java 1.0).

В Java 8 представлены новые функциональные интерфейсы, такие как Predicate, Consumer и Function. Чтобы узнать больше об этом, смотрите руководство по функциональным интерфейсам в Java 8.

Заключение

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

Полные примеры кода доступны на GitHub.

Оригинал