Интерфейсы в Java: Полное руководство для начинающих

Author: Tatyana Milkina
  1. Что такое интерфейс?
  2. Внутренние интерфейсы
  3. Расширение интерфейсов
  4. Интерфейсы маркеры
  5. Методы по умолчанию в интерфейсах
  6. Статические методы интерфейса

1. Что такое интерфейс в Java?

Интерфейс это конструкция языка Java, в рамках которой принято описывать абстрактные публичные (abstract public) методы и статические константы (final static).

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

Рассмотрим следующую картинку. У нас есть контракт (интерфейс), в котором описано какие действия должна выполнять мышка. Это например, клик по правой клавише и клик по левой. Разные производители мышки (классы), реализующие данный контракт (интерфейс), обязаны спроектировать мышки, у которых будут эти действия. Но как выглядят мышки, какие дополнительные опции будут иметь - все это решает сам производитель. 

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

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

Переменные интерфейса являются public static final по умолчанию и эти модификаторы необязательны при их объявлении. Например, в следующем примере объявлены переменные RIGHT, LEFT, UP, DOWN без каких-либо модификаторов. Но они будут public static final.

Все методы интерфейса являются public abstract и эти модификаторы тоже необязательны. Объявляемые методы не содержат тел, их объявления завершаются точкой с запятой:

public interface Moveable {
    int RIGHT = 1;
    int LEFT = 2;
    int UP = 3;
    int DOWN = 4;

    void moveRight();

    void moveLeft();
}

Чтобы указать, что данный класс реализует интерфейс, в строке объявления класса указываем ключевое слово implements и имя интерфейса. Класс реализующий интерфейс должен содержать полный набор методов, определенных в этом интерфейсе. Но в каждом классе могут быть определены и свои методы. Например, следующий класс Transport реализует интерфейс MoveableВ нем реализованы методы moveRight() и moveLeft() интерфейса Moveable, и добавлены свои методы stop(), start():

public class Transport implements Moveable {
    public void moveRight() {
        System.out.println("Транспорт поворачивает вправо.");
    }

    public void moveLeft() {
        System.out.println("Транспорт поворачивает влево.");
    }

    public void stop() {
        System.out.println("Транспорт останавливается.");
    }

    public void start() {
        System.out.println("Транспорт стартует.");
    }
}

Один интерфейс может быть реализован любым количеством классов. Например, в следующей схеме добавлены еще два класса Robot и Device, которые тоже реализуют интерфейс Moveable.

Один интерфейс может быть реализован любым количеством классов фото

Класс Robot из вышеуказанной схемы:

public class Robot implements Moveable {
    public void moveRight() {
        System.out.println("Робот поворачивает вправо.");
    }

    public void moveLeft() {
        System.out.println("Робот поворачивает влево.");
    }
}

Если класс реализует интерфейс, но не полностью реализует определенные в нем методы, он должен быть объявлен как abstract

Например, класс Device реализует только один метод интерфейса Moveable, поэтому он абстрактный:

public abstract class Device implements Moveable {
    public void moveRight() {
        System.out.println("Девайс поворачивает вправо.");
    }
}

Тип интерфейса можно указывать при объявлении переменных, содержащих ссылки на объекты, классы которых реализуют этот интерфейс. Например, в следующем примере переменная moveable имеет тип Moveable, и указывает она на объект Transport. Но на основе интерфейсов нельзя порождать объекты. Например, в строке Moveable moveable1 = new Moveable() будет ошибка компиляции. При использовании переменной типа интерфейс, доступны только те члены класса, которые определены в этом интерфейсе. Например, нельзя вызвать метод start(), используя переменную moveable. А для переменной transport можно:

public class TransportExample {
    public static void main(String[] args) {
        Moveable moveable = new Transport();
        Transport transport = new Transport();
        Moveable robot = new Robot();
        //Moveable moveable1 = new Moveable();

        //  moveable.start();
        moveable.moveRight();
        moveable.moveLeft();
        System.out.println();
        transport.start();
        transport.moveRight();
        transport.moveLeft();
        transport.stop();
        System.out.println();

        robot.moveLeft();
        robot.moveRight();
    }
}

Один класс может реализовать любое количество интерфейсов. На следующей схеме показан класс Pickup, который реализует два интерфейса CargoAuto и PassengersAuto:

Один класс может реализовать любое количество интерфейсов фото

public interface CargoAuto {
    void transportCargo();
}
public interface PassengersAuto {
    void transportPassengers();
}

Для указания того, что класс реализует несколько интерфейсов, после ключевого слова implements через запятую перечисляются нужные интерфейсы. Класс Pickup должен определить все методы реализуемых интерфейсов:

public class Pickup implements CargoAuto, PassengersAuto {
    public void transportCargo() {
        System.out.println("Везу груз");
    }

    public void transportPassengers() {
        System.out.println("Везу пассажиров");
    }
}

2. Внутренние интерфейсы

Интерфейсы объявленные в классах или в других интерфейсах называются внутренние или вложенные. Например, интерфейс NestedIf определен внутри класса A:

public class A {
    public interface NestedIf {
        boolean isNotNegative(int x);
    }
}

При обращении к интерфейсу NestedIf требуется указывать имя его внешнего класса - A.NestedIf:

public class B implements A.NestedIf {
    public boolean isNotNegative(int x) {
        return x >= 0;
    }
}
public class NestedIfExample {
    public static void main(String[] args) {
        A.NestedIf nif = new B();
        if (nif.isNotNegative(10)) {
            System.out.println("Число 10 не отрицательное.");
        }
        if (nif.isNotNegative(-12)) {
            System.out.println("Этo не будет выведено.");
        }
    }
}

3. Расширение интерфейсов

Интерфейс может наследоваться от другого интерфейса через ключевое слово extends. Один интерфейс, в отличие от классов, может расширять несколько интерфейсов.

Например, интерфейс Football расширяет интерфейсы TVProgram и Sport. Класс FootballImplреализующий интерфейс Football, должен переопределить методы всех трех интерфейсов Football, TVProgram и Sport:

Расширение интерфейсов фото

public interface Sport {
    void setHomeTeam(String name);

    void setVisitingTeam(String name);
}
public interface Hockey extends Sport {
    void homeGoalScored();

    void visitingGoalScored();

    void endOfPeriod(int period);

    void overtimePeriod(int ot);
}
public interface TVProgram {
    void switchToChannel();
}
public interface Football extends Sport, TVProgram {
    void homeTeamScored(int points);

    void visitingTeamScored(int points);

    void endOfQuarter(int quarter);
}
public class FootballImpl implements Football {
    @Override
    public void setHomeTeam(String name) {
        System.out.println("Setting Home Team");
    }

    @Override
    public void switchToChannel() {
        System.out.println("Switching to channel");
    }

    @Override
    public void homeTeamScored(int points) {
        System.out.println("Scored");
    }
    @Override
    public void setVisitingTeam(String name) {
        System.out.println("Setting visiting team");
    }

    @Override
    public void visitingTeamScored(int points) {
        System.out.println("Visiting Team Scored");
    }

    @Override
    public void endOfQuarter(int quarter) {
        System.out.println("End of quarter");
    }
}

4. Интерфейсы маркеры

Интерфейсы маркеры - это интерфейсы, у которых не определены ни методы, ни переменные. Реализация этих интерфейсов придает классу определенные свойства. Например, интерфейсы Cloneable и Serializable, отвечающие за клонирование и сохранение объекта в информационном потоке, являются интерфейсами маркерами. Если класс реализует интерфейс Cloneable, это говорит о том, что объекты этого класса могут быть клонированы.

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

В JDK 8 в интерфейсы ввели методы по умолчанию - это методы, у которых есть реализация. Другое их название - методы расширения. Классы, реализующие интерфейсы, не обязаны переопределять такие методы, но могут если это необходимо. Методы по умолчанию определяются с ключевым словом default.

Интерфейс SomeInterface объявляет метод по умолчанию defaultMethod() с базовой реализацией:

public interface SomeInterface {
    default String defaultMethod() {
        return "Объект типа String по умолчанию";
    }
}

Класс SomeInterfaceImpl1, реализующий этот интерфейс, не переопределяет метод defaultMethod() - так можно. 

public class SomeInterfaceImpl1 implements SomeInterface {
}

А если класс SomeInterfaceImpl2 не устраивает реализация по умолчанию, он переопределяет этот метод: 

public class SomeInterfaceImpl2 implements SomeInterface {
    @Override
    public String defaultMethod() {
        return "Другая символьная строка";
    }
}

Создаем два объекта классов SomeInterfaceImpl1 и SomeInterfaceImpl2, и вызываем для каждого метод defaultMethod()Для объекта класса SomeInterfaceImpl1 вызовется метод, реализованный в интерфейсе, а для объекта класса SomeInterfaceImpl2 - его собственная реализация:

public class DefaultMethodExample {
    public static void main(String[] args) {
        SomeInterface obj1 = new SomeInterfaceImpl1();
        SomeInterface obj2 = new SomeInterfaceImpl2();

        System.out.println(obj1.defaultMethod());
        System.out.println(obj2.defaultMethod());
    }
}

Результат выполнения:

Объект типа String по умолчанию
Другая символьная строка

6. Статические методы интерфейса

В версии JDK 8, в интерфейсы добавлена еще одна возможность - определять в нем статические методы. Статические методы интерфейса, как и класса, можно вызывать независимо от любого объекта. Для вызова статического метода достаточно указать имя интерфейса и через точку имя самого метода.

public interface MyIf {
    int getNumber();

    static int staticMethod() {
        return 0;
    }
}
public class StaticMethodExample {
    public static void main(String[] args) {
        MyIf obj1 = new MyIfImp();

        System.out.println(obj1.getNumber());
        System.out.println(MyIf.staticMethod());
    }
}
Read also:
Comments
sbnet@bk.ru
Nov 14, 2022
По-моему ошибка в тесте, где есть классы Electronic, Phone1, Phone2, Phone3 и интерфейс Device. Там класс Phone3, реализующий интерфейс Device, не переопределяет его метод, поэтому должна быть ошибка компиляции.
sbnet@bk.ru
Nov 14, 2022
И еще где-то есть тесты, в которых объявлено сразу несколько public классов(причем он не один такой тест), не помню где. Тоже вроде ошибка.
sbnet@bk.ru
Nov 14, 2022
А еще есть тесты, которые опережают теорию :) и просят указать то, что еще не проходили по идее :)
milkina
Dec 2, 2022
Когда видите такие тесты, комментируйте их.
milkina
Dec 2, 2022
Класс Phone3, реализующий интерфейс Device, расширяет еще и класс Phone1. А класс Phone1 содержит метод doIt. Поэтому там все правильно.