Интерфейсы в Java: Полное руководство для начинающих
- Что такое интерфейс?
- Внутренние интерфейсы
- Расширение интерфейсов
- Интерфейсы маркеры
- Методы по умолчанию в интерфейсах
- Статические методы интерфейса
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());
}
}
Please log in or register to have a possibility to add comment.