Интерфейсы

Последнее обновление: 30.09.2025

Механизм наследования очень удобен, но он имеет свои ограничения. В частности мы можем наследовать только от одного класса, в отличие, например, от языка С++, где имеется множественное наследование.

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

Но суть интерфейса не только и не столько в этом. Ключевая мысли интерфейса состоит в том, чтобы определить некий набор требований, которым должен соответствовать класс. Интерфейс определяет, что должен делать класс, а класс уже определяет, как он это делает.

Определение интерфейса

Чтобы определить интерфейс, используется ключевое слово interface. Например:

interface Movable{ }

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

Применение интерфейса

Как было сказано выше, интерфейс определяет набор требований к классу, а класс применяет эти требования. Чтобы указать, что класс применяет или реализует интерфейс, после названия класса указывается ключевое слово implements, после которого идет название интерфейса:

interface Movable{ }
class Car implements Movable{ }

То есть в данном случае мы можем сказать, что класс Car применяет или реализует интерфейс Movable. Грубо говоря, мы говорим, что машина (Car) - это транспортное средство, нечто, что функционал для перемещения.

Переменные интерфейса

Интерфейс фактически представляет новый тип. И мы можем определять переменные интерфейса:

public class Program{
      
    public static void main(String[] args) {
        
        Movable obj;    // определяем интерфейс
    }
}
// определение интерфейса
interface Movable{ }

Однако все же интерфейсы - это не классы. В частности, мы не можем использовать оператор new для создания экземпляра интерфейса:

public class Program{
      
    public static void main(String[] args) {
        
        Movable obj = new Movable();        // ! Ошибка, так нельзя сделать
    }
}
interface Movable{ }

Но переменной интерфейса можно присвоить ссылку на объект класса, который применяет данный интерфейс. Например:

public class Program{
      
    public static void main(String[] args) {
        
        Movable obj = new Car();
        System.out.println(obj);
    }
}

interface Movable{ }
class Car implements Movable{ }

Поскольку класс Car применяет интерфейс Movable, то переменной этого интерфейса мы можем присвоить объект Car.

Определение компонентов интерфейса

Выше определенный интерфейс Movable был пустой и ничего не определял. Но интерфейс может определять ряд компонентов:

  • Методы без реализации

  • Методы по умолчанию (с реализацией)

  • Приватные методы

  • Статические методы (как приватные, так и публичные)

  • Константы

Определение методов в интерфейсе

Как говорилось выше, интерфейс определяет ряд требований или контракт, которым должен соответствовать класс. И эти требования определяются в интерфейсе в виде методов. Методы интерфейса могут иметь, а могут и не иметь реализации. Если некоторый класс применяет определенный интерфейс, то этот класс должен реализовать все методы применяемого интерфейса (исключение касается методов, для которых в интерфейсе определена реализация по умолчанию).

Например, изменим интерфейс Movable, добавив в него метод:

interface Movable{

    void move();
}

В данном случае интерфейс Movable представляет функционал транспортного средства и определяет один метод без реализации - метод move(), который условно предназначен для передвижения транспортного средства. Методы без реализации похожи на абстрактные методы абстрактных классов. Все методы интерфейса не имеют модификаторов доступа, но фактически по умолчанию доступ public, так как цель интерфейса - определение функционала для реализации его классом. Поэтому весь функционал должен быть открыт для реализации.

Таким образом, у нас еть интерфейс Movable, которое представляет непонятно какое транспортное средство, и есть функция move, которая предназначена для перемещения транспортного средства, но как именно это перемещение осуществляется - неизвестно.

Однако класс, который будет применять интерфейс, должен реализовать все его методы (которые не имеют реализации). Теперь реализуем интерфейс Movable:

public class Program{
      
    public static void main(String[] args) {
        
        Car car = new Car();
        car.move();     // Едем на машине
    }
}

interface Movable{

    void move();
}
// класс машины
class Car implements Movable{ 

    public void move(){

        System.out.println("Едем на машине");
    }
}

В данном случае класс Car применяет интерфейс Movable и поэтому должен реализовать его метод move(). Если класс не реализует какие-то методы интерфейса, то такой класс должен быть определен как абстрактный, но его неабстрактные классы-наследники затем все равно должны будут реализовать эти методы.

Таким образом, интерфейс Movable определяет требования - реализовать метод перемещения (метод move). Класс Car применяет эти требования, но сам решает, как конкретно реализовать метод перемещения.

Подобным образом мы можем реализовать интерфейс в серии классов. Например, добавим класс, который будет представлять самолет:

public class Program{
      
    public static void main(String[] args) {
        
        Car car = new Car();
        car.move();     // Едем на машине

        Aircraft boing = new Aircraft();
        boing.move();   // Летим на самолете
    }
}

interface Movable{

    void move();
}
// класс машины
class Car implements Movable{ 

    public void move(){

        System.out.println("Едем на машине");
    }
}
class Aircraft implements Movable{ 

    public void move(){

        System.out.println("Летим на самолете");
    }
}

Методы по умолчанию

Интерфейсы в языке Java могут иметь методы по умолчанию (default methods) - методы с реализацией по умолчанию, которая используется, если класс, реализующий данный интерфейс, не реализует метод.

Зачем нужны методы по умолчанию? Допустим, ранее был создан интерфейс, который затем реализовало множество классов. Если мы захотим внести изменения в этот интерфейс в силу каких-то новых условий, то нам также придется изменять все те методы, которые его применяют. Когда речь идет о паре классов, которые определены в одном файле с интерфейсов, проблема может показаться надуманной. Но когда Но когда было определено множество классов множеством разработчиков по всему миру в течение многих лет, как в случае с интерфейсами стандартной библиотеки Java, проблем становится более актуальной и трудно решаемой. Поскольку изменение интерфейса приведет к тому, что классы больше не будут компилироваться, поскольку не реализуют новые методы.

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

Например, ранее использованный интерфейс Movable и добавим в него новый метод, который будет методом по умолчанию:

interface Movable{

    void move();
    default void stop() { 
        System.out.println("Останавливаемся"); 
    }
}

В данном случае метод stop() определен как метод по умолчанию. Метод по умолчанию - это обычный метод без модификаторов (формально имеет модификатор public), который помечается ключевым словом default. В данном случае метод stop выводит строку на консоль. В принципе можно было вообще ничего не делать и оставить пустые фигурные скобки (default void stop() { })

Затем в классе нам необязательно этот метод реализовать:

public class Program{
      
    public static void main(String[] args) {
        
        Car car = new Car();
        car.move();     // Едем на машине
        car.stop();     // Останавливаемся
    }
}

interface Movable{

    void move();
    default void stop() { 
        System.out.println("Останавливаемся"); 
    }
}
// класс машины
class Car implements Movable{ 

    public void move(){

        System.out.println("Едем на машине");
    }
}

При желании также можно переопределить этот метод:

public class Program{
      
    public static void main(String[] args) {
        
        Movable car = new Car();
        car.move();     // Едем на машине
        car.stop();     // Машина останавливается
    }
}

interface Movable{

    void move();
    default void stop() {  }
}
// класс машины
class Car implements Movable{ 

    public void move(){

        System.out.println("Едем на машине");
    }
    // переопределяем метод по умолчанию
    public void stop(){

        System.out.println("Машина останавливается");
    }
}

Статические методы

В интерфейсах также можно определять статические методы - они аналогичны методам класса:

interface Printable {
    
    void print();
	
    static void read(){
        
        System.out.println("Read printable");
    }
}

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

public static void main(String[] args) {
        
    Printable.read();
}

Приватные методы

По умолчанию все методы в интерфейсе фактически имеют модификатор public. Однако начиная с Java 9 мы также можем определять в интерфейсе методы с модификатором private. Они могут быть статическими и нестатическими, но они не могут иметь реализации по умолчанию.

Подобные методы могут использоваться только внутри самого интерфейса, в котором они определены. То есть к примеру нам надо выполнять в интерфейсе некоторые повторяющиеся действия, и в этом случае такие действия можно выделить в приватные методы:

public class Program{
     
	public static void main(String[] args) {
         
		Operation c = new Operation(); 
		System.out.println(c.sum(1, 2));        // 3
		System.out.println(c.sum(1, 2, 4));     // 7
	}
}
class Operation implements Sumable{}

interface Sumable{

	default int sum(int a, int b){
		return sumAll(a, b);
	}
	default int sum(int a, int b, int c){
		return sumAll(a, b, c);
	}
	
    private int sumAll(int... values){
         int result = 0;
		 for(int n : values){
			 result += n;
		 }
		 return result;
    }
}

Константы в интерфейсах

Кроме методов в интерфейсах могут быть определены статические константы:

interface Stateable{

	int OPEN = 1;
	int CLOSED = 0;
	
	void printState(int n);
}

Хотя такие константы также не имеют модификаторов, но по умолчанию они имеют модификатор доступа public static final, и поэтому их значение доступно из любого места программы.

Применение констант:

public class Program{
     
	public static void main(String[] args) {
         
		WaterPipe pipe = new WaterPipe();
		pipe.printState(1);
	}
}
class WaterPipe implements Stateable{
	
	public void printState(int n){
		if(n == OPEN)
			System.out.println("Water is opened");
		else if(n == CLOSED)
			System.out.println("Water is closed");
		else
			System.out.println("State is invalid");
	}
}
interface Stateable{

	int OPEN = 1;
	int CLOSED = 0;
	
	void printState(int n);
}

Вложенные интерфейсы

Как и классы, интерфейсы могут быть вложенными, то есть могут быть определены в классах или других интерфейсах. Например:

class Printer{
    interface Printable {
    
        void print();
    }
}

При применении такого интерфейса нам надо указывать его полное имя вместе с именем класса:

class Book implements Printer.Printable {

    public void print() {
        System.out.println("Печатаем книгу");
    }  
}

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

Printer.Printable printable = new Book();
printable.print();
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.