В ряде языков программирования доступно множественное наследование. Например, в языке 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, реализующего данный интерфейс.
Что будет, если один и тот же метод определен как метод по умолчанию в одном интерфейсе, и как метод суперкласса или другого интерфейса? В этом случае:
Суперклассы имеют преимущество. Если суперкласс предоставляет конкретный метод, методы по умолчанию с теми же именами и типами параметров просто игнорируются.
Конфликт интерфейсов. Если интерфейс предоставляет метод по умолчанию, а другой интерфейс содержит метод с теми же именами и типами параметров (стандартными или нет), то необходимо разрешить конфликт, переопределив этот метод.
С первым пунктом в принципе все должно быть ясно. ПОэтому посмотрим на второй вариант.
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