GrabDuck

Интерфейсы | Java

:

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

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

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

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

В среде Netbeans (и в других IDE) уже есть специальный тип файлов, предназначенный для интерфейсов. Для добавления интерфейса в проект можно нажать правой кнопкой мыши на пакет и в появившемся контекстном меню выбрать Новый-> Интерфейс Java и затем в диалоговом окне установить имя нового интерфейса:

Интерфейсы в Java

Либо можно добавить обычный файл с расширением .java и в нем уже написать код интерфейса.

Определим следующий интерфейс:

public interface Printable{

    void print();
}

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

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

И также при объявлении интерфейса надо учитывать, что только один интерфейс в файле может иметь тип доступа public. А его название должно совпадать с именем файла. Остальные интерфейсы (если такие имеются в файле java) не должны иметь модификаторов доступа.

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

class Book implements Printable{

    String name;
    String author;
    int year;

    Book(String name, String author, int year){
        
        this.name = name;
        this.author = author;
        this.year = year;
    }
    
    public void print() {
    
        System.out.printf("Книга '%s' (автор %s) была издана в %d году \n", name, author, year);
    }
}

При этом надо учитывать, что если класс применяет интерфейс, то он должен реализовать все методы интерфейса, как в случае выше реализован метод print.

Потом в главном классе мы можем использовать данный класс и его метод print:

Book b1 = new Book("Война и мир", "Л. Н. Толстой", 1863);
b1.print();

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

Printable pr = new Printable();
pr.print();

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

public class Journal implements Printable {

    private String name;
 
    String getName(){
        return name;
    }
 
    Journal(String name){
         
        this.name = name;
    }
    public void print() {
        System.out.printf("Журнал '%s'\n", name);
    }  
}

Класс Book и класс Journal связаны тем, что они реализуют интерфейс Printable. Поэтому мы динамически в программе можем создавать объекты Printable как экземпляры обоих классов:

Printable printable = new Book("Война и мир", "Л. Н. Толстой", 1863);
printable.print();
printable = new Journal("Хакер");
printable.print();

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

public static void main(String[] args) {
        
    Printable printable = createPrintable("Компьютерра",false);
    printable.print();
        
    read(new Book("Отцы и дети", "И. Тургенев", 1862));
    read(new Journal("Хакер"));
}
    
static void read(Printable p){
        
    p.print();
}
    
static Printable createPrintable(String name, boolean option){
        
    if(option)
        return new Book(name, "неизвестен", 2015);
    else
        return new Journal(name);
}

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

Интерфейсы в Java

Метод read() в качестве параметра принимает объект интерфейса Printable, поэтому в этот метод мы можем передать как объект Book, так и объект Journal.

Метод createPrintable() возвращает объект Printable, поэтому также мы вожем возвратить как объект Book, так и Journal.

Интерфейсы в преобразованиях типов

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

Printable p =new Journal("Хакер");
p.print();
        
// Интерфейс не имеет метода getName, необходимо явное приведение
String name = ((Journal)p).getName();
System.out.println(name);

И если мы хотим обратиться к методам класса Journal, которые определены не в интерфейсе Printable, а в самом классе Journal, то нам надо явным образом выполнить преобразование типов: ((Journal)p).getName();

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

Ранее до JDK 8 при реализации интерфейса мы должны были обязательно реализовать все его методы в классе. А сам интерфейс мог содерать только определения методов без конкретной реализации. В JDK 8 была добавлена такая функциональность как методы по умолчанию. И теперь интерфейсы кроме определения методов могут иметь их реализацию по умолчанию, которая используется, если класс, реализующий данный интерфейс, не реализует метод. Например, создадим метод по умолчанию в интерфейсе Printable:

interface Printable {
    
    default void print(){
        
		System.out.println("Неизвестное печатное издание");
    }
}

Метод по умолчанию - это обычный метод без модификаторов, который помечается ключевым словом default. Затем в классе Journal нам необязательно этот метод реализовать, хотя мы можем его и переопределить:

public class Journal implements Printable {

    private String name;
 
    String getName(){
        return name;
    }
    Journal(String name){
         
        this.name = name;
    }
}

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

Начиная с JDK 8 в интерфейсах доступны статические методы - они аналогичны методам класса:

interface Printable {
    
    void print();
	
    static void read(){
        
        System.out.println("Чтение печатного издания");
    }
}

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

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

Дополнительно об интерфейсах

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

interface Printable {
    
    // методы интерфейса
}

interface Searchable {
    
    // методы интерфейса
}

class Book implements Printable, Searchable{

    // реализация класса
}

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

interface BookPrintable extends Printable{
    
	void paint();
}

При применении этого интерфейса класс Book должен будет реализовать как методы интерфейса BookPrintable, так и методы базового интерфейса Printable.

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

interface Printable{
    
	void print();
    int MAX_NUMBER = 400;
}

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

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

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

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

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

public class Journal implements Printer.Printable {

    String name;
 
    Journal(String name){
         
        this.name = name;
    }
    public void print() {
        System.out.printf("Журнал '%s'\n", name);
    }  
}

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

Printer.Printable p =new Journal("Хакер");
p.print();