Интерфейс в Java: что это такое, implements и примеры
Интерфейс в Java — это ссылочный тип, который задаёт контракт: какие методы должен предоставить класс, без описания того, как они работают. Класс подключает интерфейс ключевым словом implements и переопределяет все его абстрактные методы. Один класс может реализовать несколько интерфейсов, поэтому интерфейсы — основа полиморфизма и слабой связанности кода.
Классически интерфейс содержит абстрактные публичные методы (public abstract) и статические константы (public static final). Начиная с Java 8 в интерфейсе могут быть также методы по умолчанию (default) и статические методы, а с Java 9 — приватные методы.
Что такое интерфейс в Java
С помощью интерфейса можно указать, что именно должен выполнять класс, его реализующий, но не как это делать. Способ реализации выбирает сам класс. У интерфейса нет полей экземпляра, поэтому он не хранит состояние объекта: переменные, объявленные в интерфейсе, — это всегда константы public static final. Интерфейсы — один из основных механизмов полиморфизма в Java: один интерфейс, много реализаций.
Рассмотрим следующую картинку. У нас есть контракт (интерфейс), в котором описано, какие действия должна выполнять мышка. Это, например, клик по правой клавише и клик по левой. Разные производители мышек (классы), реализующие данный контракт, обязаны спроектировать мышки, у которых будут эти действия. Но как выглядят мышки и какие дополнительные опции они имеют — решает сам производитель.

Интерфейсы, как и классы, могут быть объявлены с уровнем доступа public или package-private (без модификатора — тогда интерфейс виден только внутри своего пакета).
Как создать интерфейс в Java: пошагово
- Создайте файл с именем интерфейса, например
Moveable.java. Как и публичный класс,public-интерфейс должен лежать в файле с тем же именем. - Объявите интерфейс ключевым словом
interfaceвместоclass:public interface Moveable { ... }. - Опишите методы без тела — только сигнатуру и точку с запятой. При необходимости добавьте константы.
- Создайте класс, который реализует интерфейс через
implements, и переопределите в нём все абстрактные методы (см. следующий раздел).
Переменные интерфейса являются 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();
} Совет
Константы в интерфейсе допустимы, но интерфейс, который существует только ради констант, считается антипаттерном (Constant Interface). Для набора связанных значений вроде направлений движения в современном коде лучше использовать enum.
Имплементация интерфейса: implements
Реализацию интерфейса классом часто называют имплементацией (от англ. implement — реализовывать). Чтобы указать, что класс реализует интерфейс, в строке объявления класса пишут ключевое слово implements и имя интерфейса. Правила имплементации:
- класс обязан переопределить все абстрактные методы интерфейса (и интерфейсов, от которых тот унаследован);
- реализующий метод должен быть объявлен как
public— методы интерфейса публичные, а сужать доступ при переопределении нельзя; - аннотация
@Overrideнеобязательна, но полезна: компилятор проверит, что сигнатура совпадает с интерфейсной; default-методы переопределять не нужно — класс получает их реализацию из интерфейса;- если реализованы не все абстрактные методы, класс должен быть абстрактным (
abstract).
Кроме методов интерфейса, в классе могут быть определены и собственные методы. Например, следующий класс Transport реализует интерфейс Moveable: в нём реализованы методы moveRight() и moveLeft() и добавлены свои методы 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, поэтому он абстрактный. Оставшийся метод moveLeft() обязан реализовать первый неабстрактный подкласс Device:
public abstract class Device implements Moveable {
public void moveRight() {
System.out.println("Девайс поворачивает вправо.");
}
} Класс может одновременно наследовать другой класс и реализовывать интерфейсы. Порядок в объявлении фиксирован: сначала extends, потом implements:
public class ElectricCar extends Car implements Moveable, Chargeable {
// реализация методов Moveable и Chargeable
} Интерфейс как тип переменной
Переменная объявлена как Moveable moveable = new Transport();, в классе Transport есть метод start() — и всё равно строка moveable.start(); не компилируется. Компилятор смотрит на тип переменной, а не на реальный объект, и разрешает вызывать только то, что обещано интерфейсом.
Тип интерфейса можно указывать при объявлении переменных, которые ссылаются на объекты классов, реализующих этот интерфейс. При этом создать объект самого интерфейса нельзя: строка Moveable moveable1 = new Moveable(); вызовет ошибку компиляции. Метод start() доступен через переменную transport типа Transport, но не через moveable:
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();
}
} Результат выполнения:
Транспорт поворачивает вправо.
Транспорт поворачивает влево.
Транспорт стартует.
Транспорт поворачивает вправо.
Транспорт поворачивает влево.
Транспорт останавливается.
Робот поворачивает влево.
Робот поворачивает вправо. Реализация нескольких интерфейсов
Один класс может реализовать любое количество интерфейсов. Так в Java решается задача множественного наследования типов: наследовать можно только один класс, а реализовать — сколько угодно интерфейсов. На следующей схеме показан класс 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("Везу пассажиров");
}
} Внутренние интерфейсы
Интерфейсы, объявленные в классах или в других интерфейсах, называются внутренними или вложенными. Вложенный интерфейс всегда неявно static. Например, интерфейс 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("Это не будет выведено.");
}
}
} Из стандартной библиотеки хорошо известен вложенный интерфейс Map.Entry — пара «ключ–значение» внутри интерфейса Map.
Расширение интерфейсов
Интерфейс может наследоваться от другого интерфейса с помощью ключевого слова extends. В отличие от классов, один интерфейс может расширять сразу несколько интерфейсов.
На схеме ниже от базового интерфейса Sport унаследованы два разных интерфейса — Hockey и Football. Это показывает, что один интерфейс могут расширять сколько угодно других: каждый добавляет свои методы. Интерфейс Football при этом расширяет сразу два интерфейса — Sport и TVProgram.

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);
} Класс FootballImpl, реализующий интерфейс Football, должен переопределить методы всех трёх интерфейсов: Football, TVProgram и Sport. Методы Hockey ему реализовывать не нужно — это другая ветка иерархии, и класс, реализующий Hockey, реализовал бы методы Hockey и Sport:
public class FootballImpl implements Football {
@Override
public void setHomeTeam(String name) {
System.out.println("Setting Home Team");
}
@Override
public void setVisitingTeam(String name) {
System.out.println("Setting visiting team");
}
@Override
public void switchToChannel() {
System.out.println("Switching to channel");
}
@Override
public void homeTeamScored(int points) {
System.out.println("Scored");
}
@Override
public void visitingTeamScored(int points) {
System.out.println("Visiting Team Scored");
}
@Override
public void endOfQuarter(int quarter) {
System.out.println("End of quarter");
}
} Начиная с Java 17 круг реализаций интерфейса можно ограничить: sealed interface Shape permits Circle, Square разрешает реализовывать Shape только перечисленным классам.
Методы по умолчанию в интерфейсах
В Java 8 в интерфейсы добавили методы по умолчанию (default-методы) — методы, у которых есть реализация прямо в интерфейсе. В спецификации Java 8 их изначально называли виртуальными методами расширения (virtual extension methods). Классы, реализующие интерфейс, не обязаны переопределять такие методы, но могут, если это необходимо. Методы по умолчанию объявляются с ключевым словом default.
Главная причина их появления — эволюция API: благодаря default-методам в интерфейс Collection удалось добавить stream() и forEach(), не сломав миллионы существующих реализаций.
Интерфейс 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 по умолчанию
Другая символьная строка Конфликт default-методов
Если класс реализует два интерфейса, в каждом из которых есть default-метод с одинаковой сигнатурой, компилятор не может выбрать, какую реализацию использовать, и выдаёт ошибку. Класс обязан переопределить такой метод сам. Чтобы вызвать реализацию конкретного интерфейса, используется синтаксис ИмяИнтерфейса.super.метод():
interface Camera {
default String info() {
return "Камера";
}
}
interface Phone {
default String info() {
return "Телефон";
}
}
public class Smartphone implements Camera, Phone {
@Override
public String info() {
return Phone.super.info() + " с функцией: " + Camera.super.info();
}
} Правила разрешения конфликтов такие: метод класса (в том числе унаследованный от суперкласса) всегда важнее default-метода интерфейса; более конкретный интерфейс (наследник) важнее своего родителя; во всех остальных случаях конфликт должен разрешить сам программист.
Статические методы интерфейса
В Java 8 в интерфейсы добавлена ещё одна возможность — определять в них статические методы. Статические методы интерфейса, как и статические методы класса, вызываются без объекта: достаточно указать имя интерфейса и через точку имя метода. Обычно в них размещают вспомогательные и фабричные методы, например List.of() или Comparator.naturalOrder().
public interface MyIf {
int getNumber();
static int staticMethod() {
return 0;
}
} public class MyIfImp implements MyIf {
@Override
public int getNumber() {
return 42;
}
} public class StaticMethodExample {
public static void main(String[] args) {
MyIf obj1 = new MyIfImp();
System.out.println(obj1.getNumber());
System.out.println(MyIf.staticMethod());
// obj1.staticMethod(); // ошибка компиляции
// MyIfImp.staticMethod(); // ошибка компиляции
}
} Результат выполнения:
42
0 Важно
В отличие от статических методов класса, статические методы интерфейса не наследуются: их нельзя вызвать ни через объект, ни через имя класса-реализации. Правильный вызов только один — MyIf.staticMethod().
Приватные методы интерфейса
С Java 9 в интерфейсе можно объявлять private-методы (обычные и статические). Они видны только внутри интерфейса и нужны, чтобы вынести общий код из нескольких default- или static-методов, не делая его частью публичного контракта:
public interface Logger {
default void info(String msg) {
log("INFO", msg);
}
default void error(String msg) {
log("ERROR", msg);
}
private void log(String level, String msg) {
System.out.println("[" + level + "] " + msg);
}
} Функциональные и маркерные интерфейсы
Функциональный интерфейс
Функциональный интерфейс — это интерфейс ровно с одним абстрактным методом (default- и static-методы не считаются). Его можно реализовать не отдельным классом, а лямбда-выражением или ссылкой на метод. Аннотация @FunctionalInterface необязательна, но заставляет компилятор проверить, что абстрактный метод действительно один:
@FunctionalInterface
public interface Calculator {
int apply(int a, int b);
}
Calculator sum = (a, b) -> a + b;
System.out.println(sum.apply(2, 3)); // 5 В JDK уже есть готовые функциональные интерфейсы — Runnable, Comparator, Function, Predicate, Supplier. Подробно они разобраны в уроках «Функциональные интерфейсы» и «Специализации функциональных интерфейсов».
Маркерные интерфейсы (интерфейсы-маркеры)
Маркерный интерфейс — это интерфейс, в котором не объявлено ни одного метода и ни одной константы. Реализуя такой интерфейс, класс «помечает» себя, а JVM или библиотеки проверяют эту метку через instanceof и меняют поведение. Классические примеры — Serializable (объекты класса можно сериализовать) и Cloneable.
Если класс реализует Cloneable, метод Object.clone() для его объектов создаёт копию, а не бросает CloneNotSupportedException. Сам Cloneable метода clone() не содержит — его всё равно нужно переопределить в классе.
Не путайте interface и @interface: запись public @interface Audit {} объявляет не обычный интерфейс, а аннотацию, которую ставят над классом или методом (@Audit), а не реализуют через implements.
Современный подход
В новом коде вместо собственных маркерных интерфейсов обычно используют аннотации (например, @Entity, @FunctionalInterface). Маркерный интерфейс оправдан, когда метка должна быть частью типа и проверяться компилятором.
Что может содержать интерфейс: сводная таблица
| Элемент | Модификаторы (неявные) | Есть тело | С какой версии Java |
|---|---|---|---|
| Константа | public static final | — (значение обязательно) | 1.0 |
| Абстрактный метод | public abstract | Нет | 1.0 |
| Вложенный класс/интерфейс | public static | Да | 1.1 |
| Метод по умолчанию | public + default | Да | 8 |
| Статический метод | public static (по умолчанию) | Да | 8 |
| Приватный метод | private или private static | Да | 9 |
| Поле экземпляра, конструктор | — | — | Недопустимы |
На чём чаще всего ошибаются
- Реализация метода без
public. Абстрактные методы интерфейса всегдаpublic, а при переопределении нельзя сужать доступ. Методvoid moveRight() {}в классе без модификатора вызовет ошибку компиляции «attempting to assign weaker access privileges». - Неверный порядок в объявлении класса.
class ElectricCar implements Moveable extends Carне скомпилируется:extendsвсегда пишется передimplements. - Попытка изменить константу интерфейса.
Moveable.RIGHT = 5;не скомпилируется: переменная неявноfinal. - Вызов метода реализации через переменную интерфейсного типа.
moveable.start()недоступен, хотя объект —Transport. Нужна переменная типаTransportили приведение типа. - Вызов статического метода интерфейса через объект или класс-реализацию — ошибка компиляции, вызывать нужно через имя интерфейса.
- Два интерфейса с одинаковым default-методом без переопределения в классе — ошибка компиляции.
- Ожидание, что
Cloneableдаёт методclone(). Интерфейс пустой, метод нужно переопределить самостоятельно.
Часто задаваемые вопросы
Что означает implements в Java и чем он отличается от extends?
Ключевое слово implements означает, что класс реализует (имплементирует) интерфейс и берёт на себя обязательство переопределить все его абстрактные методы. extends используется для наследования: класс расширяет один класс, а интерфейс расширяет один или несколько интерфейсов. Класс может сочетать оба слова: class A extends B implements C, D.
Чем интерфейс отличается от абстрактного класса в Java?
Класс может наследовать только один абстрактный класс, но реализовать любое количество интерфейсов. У абстрактного класса могут быть поля экземпляра, конструкторы и методы с любым модификатором доступа, а интерфейс хранит только константы и не имеет конструкторов. Абстрактный класс выбирают для общего состояния и кода родственных классов, интерфейс — для описания возможности (контракта), которую могут иметь несвязанные классы.
Можно ли создать объект интерфейса?
Напрямую нет: new Moveable() — ошибка компиляции. Но можно создать анонимный класс, реализующий интерфейс (new Moveable() { ... }), а для функционального интерфейса — лямбда-выражение. В обоих случаях создаётся объект класса-реализации, а не самого интерфейса.
Может ли у интерфейса быть конструктор?
Нет. У интерфейса нет полей экземпляра, которые нужно было бы инициализировать, и его нельзя создать через new, поэтому конструктор не нужен и запрещён. Константы интерфейса инициализируются при объявлении.
Поддерживает ли Java множественное наследование через интерфейсы?
Да, множественное наследование типов: класс может реализовать несколько интерфейсов, а интерфейс — расширить несколько интерфейсов. Множественного наследования состояния нет, так как интерфейсы не имеют полей экземпляра. Если из разных интерфейсов приходят одинаковые default-методы, класс обязан переопределить метод.
Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments