Интерфейсы

В Java можно унаследовать только один класс. Ключевое слово extends означает отношение «является». class Nexus5 extends MobilePhone означает, что Nexus5 является мобильным телефоном (при этом любой мобильный телефон является телефоном, а любой телефон — обектом).

Интерфейсы же представляют отношение «может», «умеет», «реализует», «поддерживает», «выполняет определённое соглашение» — это выражается ключевым словом implements.

Интерфейс предельно абстрактен: все его методы абстрактны и публичны, все его поля — константы.

Общий пример на интерфейсы

Есть различные геометрические фигуры. При этом у прямоугольника и круга есть чётко отпеределённый центр, а, например, у дуги и треугольника нет чётко определённого центра — есть геометрический центр, центр тяжести и так далее. Пусть прямоугольники и круги реализуют интерфейс HasCenter.

Интерфейс HasCenter состоит из единственного метода getCenter, который должен вернуть точку на плоскости (Point).

package online.javanese.basics.oop.interfaces;

public interface HasCenter {
    Point getCenter();
}

Класс Point описывает неизменяемую точку на поверхности.

package online.javanese.basics.oop.interfaces;

public final class Point {

    private final double x;
    private final double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    @Override
    public String toString() {
        return "Point(" + x + "; " + y + ")";
    }
}

Класс Rectangle, который описывает неизменяемый прямоугольник на поверхности и реализует интерфейс HasCenter.

package online.javanese.basics.oop.interfaces;

public final class Rectangle implements HasCenter {

    // левый верхний угол
    private final double x1;
    private final double y1;

    // правый нижний угол
    private final double x2;
    private final double y2;

    public Rectangle(double x1, double y1, double x2, double y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    @Override
    public Point getCenter() {
        return new Point((x1 + x2) / 2, (y1 + y2) / 2);
    }

    @Override
    public String toString() {
        return "Rectangle(" + x1 + "; " + y1 + " — " + x2 + "; " + y2 + ")";
    }

    public static void main(String[] args) {
        // тест
        Rectangle rect = new Rectangle(1, 1, 3, 3);
        System.out.println(rect);
        // Rectangle(1.0; 1.0 — 3.0; 3.0)
        System.out.println(rect.getCenter());
        // Point(2.0; 2.0)
    }

}

Круг

package online.javanese.basics.oop.interfaces;

public final class Circle implements HasCenter {

    private final Point center;
    private final double radius;

    public Circle(Point center, double radius) {
        this.center = center;
        this.radius = radius;
    }

    public Circle(double centerX, double centerY, double radius) {
        this.center = new Point(centerX, centerY);
        this.radius = radius;
    }

    @Override
    public Point getCenter() {
        return center;
    }

    @Override
    public String toString() {
        return "Circle(" +
                "at (" + center.getX() + "; " + center.getY() + "), " +
                "r=" + radius + ")";
    }

    public static void main(String[] args) {
        // тест
        Circle circle = new Circle(8, 8, 5);
        System.out.println(circle);
        // Circle(at (8.0; 8.0), r=5.0)
        System.out.println(circle.getCenter());
        // Point(8.0; 8.0)
    }

}

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

package online.javanese.basics.oop.interfaces;

public class PositiveQuarterTest {

    public static void main(String[] args) {
        check(new Circle(1, 1, 1));
        // Circle(at (1.0; 1.0), r=1.0) in positive quarter: true

        check(new Circle(0, 0, 100500));
        // Circle(at (0.0; 0.0), r=100500.0) in positive quarter: false

        check(new Rectangle(0, 0, 22, 74));
        // Rectangle(0.0; 0.0 — 22.0; 74.0) in positive quarter: true

        check(new Rectangle(-1, -1, 0, 0));
        // Rectangle(-1.0; -1.0 — 0.0; 0.0) in positive quarter: false
    }

    private static void check(HasCenter shape) {
        System.out.println(shape + " in positive quarter: " + isInPositiveQuarter(shape));
    }

    private static boolean isInPositiveQuarter(HasCenter shape) {
        Point center = shape.getCenter();
        return center.getX() > 0 && center.getY() > 0;
    }

}

Интерфейсы часто играют роль дополнительных характеристик и позволяют объединить классы из разных иерархий.

Слушатель событий как реализация интерфейса

    // этот класс
    public static abstract class OnDialListener {
        public abstract void onDial(String number);
    }
    
    // легко можно заменить интерфейсом
    public interface OnDialListener {
        void onDial(String number);
    }
    
    
    // тогда локальный класс
    class MyListener extends Nexus5.OnDialListener {
    // становится реализацией интерфейса,
    class MyListener implements Nexus5.OnDialListener {
    
    
    // а код анонимного класса
    new OnDialListener() {
        // переопределение методов
    }
    // остаётся неизменным

Что выбрать?

У интерфейсов не может быть состояния (полей). Если это вас устроит — выбирайте интерфейс. Если нет — абстрактный класс.

Например, класс Phone в начале главы имел единственный абстрактный метод dial и мог бы быть интерфейсом. Но к середине главы он обрёл поле onDialListener (состояние) и финальный метод dial (поведение).

Начиная с Java 8 (2014 год), у интерфейсов может быть реализация методов (поведение), но эти методы не могут быть финальными, потому что интерфейсы предназначены для наследования чуть более, чем полностью. Поэтому Phone — класс.

Комментарии к уроку

Сообщить об ошибке