Множественная реализация и наследование интерфейсов

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

В ряде языков программирования доступно множественное наследование. Например, в языке C++ можно наследовать один класс от нескольких базовых классов. В Java множественное наследование недоступно, однако мы можем использовать множественную реализацию интерфейсов, когда один класс применяет сразу несколько интерфейсов.

Если нам надо применить в классе несколько интерфейсов, то они все перечисляются через запятую после слова implements:

public class Program{
      
    public static void main(String[] args) {
        
        TextFile file = new TextFile("content.txt", "Hello World");
        System.out.println("Печать файла " + file.getName());
        file.print();
    }
}

interface File{
    String getName();  // возвращает имя файла
}
interface Printable{
    void print();  // печать содержимого
}
class TextFile implements File, Printable{

    private String name;
    private String text;

    TextFile(String name, String text){

        this.name = name;
        this.text = text;
    }
    public String getName(){ return this.name; }
    public void print() { System.out.println(text); }
}

В данном случае класс TextFile реализует два интерфейса: File и Printable, поэтому класс обязан реализовать методы обоих этих интерфейсов. Это также означает, что объект класса TextFile может рассматриваться и как объект File, и как объект Printable:

public class Program{
      
    public static void main(String[] args) {
        
        TextFile file = new TextFile("content.txt", "Hello World");
        printFileName(file);
        printObj(file);
    }

    static void printFileName(File file){
        System.out.println("File name: " + file.getName());
    }

    static void printObj(Printable obj){
        obj.print();
    }
}

В данном случае метод printFileName принимает объект интерфейса File, соответвенно мы можем передать в метод объект любого класса, который реализует этот интерфейс. Аналогично метод printObj() принимает объект интерфейса Printable, вместо которого можно передать объект класса TextFile, реализующего данный интерфейс.

Конфликты методов по умолчанию

Что будет, если один и тот же метод определен как метод по умолчанию в одном интерфейсе, и как метод суперкласса или другого интерфейса? В этом случае:

  1. Суперклассы имеют преимущество. Если суперкласс предоставляет конкретный метод, методы по умолчанию с теми же именами и типами параметров просто игнорируются.

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

С первым пунктом в принципе все должно быть ясно. ПОэтому посмотрим на второй вариант.

public class Program{
      
    public static void main(String[] args) {
        
        EBook book = new EBook();
        book.print();
    }
}
// печать на принтере
interface Printable{
    default void print(){
        System.out.println("Печатаем на принтере");
    }
}
// работа с консолью
interface Console{
    default void print(){
        System.out.println("Выводим данные на консоль");
    }
}
class EBook implements Printable, Console{ }

Здесь класс EBook представляет класс электронной книги. Он реализует два интерфейса: интерфейс Printable представляет печать на принтере с помощью метода print и интерфейс Console, который представляет работу с консолью, в частности вывод на консоль с помощью метода print. Применение двух интерфейсов логично: мы хотим, чтобы электронную книгу можно было распечатать на принтере, а ее текст можно было бы вывести на консоль. Но поскольку в обоих интерфейсах определен метод с одним и тем же именем, мы столкнемся с ошибкой на этапе компиляции:

Program.java:20: error: types Printable and Console are incompatible;
class EBook implements Printable, Console{ }
^
  class EBook inherits unrelated defaults for print() from types Printable and Console
1 error

Посмотрим, какие есть варианты для разрешения этой двойственности

Явное определение метода в классе

В этом случае мы можем предоставить реализацию метода print в классе EBook:

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

Причем в данном случае неважно, как мы будем рассматривать объект EBook - как объект Printable, или как объект Console, метод print все равно будет работать:

Console book = new EBook();
// или так 
// Printable book = new EBook();
book.print(); // Печатаем книгу

Выбор реализации определенного интерфейса

В качестве альтернативы мы можем выбрать реализацию из определенного интерфейса:

public class Program{
      
    public static void main(String[] args) {
        
        Console book = new EBook();
        book.print();
    }
}
// печать на принтере
interface Printable{
    default void print(){
        System.out.println("Печатаем на принтере");
    }
}
// работа с консолью
interface Console{
    default void print(){
        System.out.println("Выводим данные на консоль");
    }
}
class EBook implements Printable, Console{ 
    
    public void print(){
        Printable.super.print();
    }
}

С помощью выражения Printable.super.print() в методе print класса EBook мы выбираем реализацию из интерфейса Printable. ПРичем это будет работать вне зависимости, какой интерфейс представляет переменная, даже если это переменная интерфейса Console:

Console book = new EBook();
book.print();   // Печатаем на принтере

Наследование интерфейсов

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

interface Printable{
    void print();  // печать содержимого
}
interface File extends Printable{
    String getName();  // возвращает имя файла
}

В данном случае интерфейс File наследует интерфейс Printable. А это значит, что класс, который реализует интерфейс File, должен будет реализовать как методы интерфейса File, так и методы базового интерфейса Printable. Например:

public class Program{
      
    public static void main(String[] args) {
        
        TextFile file = new TextFile("content.txt", "Hello World");
        System.out.println("File name: " + file.getName());
        file.print();
    }
}

interface Printable{
    void print();  // печать содержимого
}
interface File extends Printable{
    String getName();  // возвращает имя файла
}
class TextFile implements File{

    private String name;
    private String text;

    TextFile(String name, String text){

        this.name = name;
        this.text = text;
    }
    public String getName(){ return this.name; }
    public void print() { System.out.println(text); }
}

Консольный вывод:

File name: content.txt
Hello World
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.