Интерфейси и абстрактни класове

Интерфейси и сравнение с абстрактни класове

Интерфейсът в Java описва договор за поведение. Той задава какви методи трябва да бъдат реализирани от класовете, които го имплементират. Интерфейсът не описва конкретен обект самостоятелно, а описва способност или роля, която различни класове могат да имат.

Декларация на интерфейс

Интерфейс се декларира чрез ключовата дума interface, следвана от име и тяло, оградено с фигурни скоби. В тялото се описват методите, които класовете трябва да реализират.

interface Printable {

    void print();
}

Методът print() няма тяло. Той описва действие, което трябва да съществува във всеки конкретен клас, който имплементира интерфейса Printable.

Имплементиране на интерфейс

Клас имплементира интерфейс чрез ключовата дума implements.

class Document implements Printable {

    @Override
    public void print() {
        System.out.println("Printing document");
    }
}

Методите от интерфейс са публични като договор. При реализацията им в клас трябва да се използва public, защото не може да се намалява видимостта на наследеното поведение.

Ако конкретен клас имплементира интерфейс, той трябва да реализира всички абстрактни методи от този интерфейс. Ако не ги реализира, самият клас трябва да бъде деклариран като abstract.

Интерфейс като тип

Интерфейсът може да се използва като тип на променлива, параметър или масив.

Printable printable = new Document();
printable.print();

Променливата има тип Printable, а реалният обект е Document. Това позволява различни класове да се обработват еднакво, ако имплементират един и същ интерфейс.

Няколко интерфейса

Един клас може да имплементира повече от един интерфейс.

interface Movable {

    void move();
}

interface Chargeable {

    void charge();
}

class ElectricCar implements Movable, Chargeable {

    @Override
    public void move() {
        System.out.println("Moving");
    }

    @Override
    public void charge() {
        System.out.println("Charging");
    }
}

Това е възможно, защото интерфейсите описват поведение, а не наследяване на състояние от няколко класа.

Съдържание на интерфейс

Интерфейс може да съдържа:

  • абстрактни методи;
  • default методи;
  • static методи;
  • константи.
interface Identifiable {

    int MIN_ID = 1;

    int getId();

    default boolean hasValidId() {
        return getId() >= MIN_ID;
    }

    static String getTypeName() {
        return "Identifiable";
    }
}

Поле в интерфейс е константа. То се третира като public static final, дори когато тези модификатори не са изписани.

Абстрактни методи в интерфейс

Метод без тяло в интерфейс е абстрактен метод. Той описва действие, което класът трябва да реализира.

interface Printable {

    void print();
}

Методът print() няма тяло. Всеки конкретен клас, който имплементира Printable, трябва да предостави реализация.

default методи

default методът е метод в интерфейс, който има тяло. Той предоставя стандартна реализация, която класовете могат да използват директно или да предефинират.

interface Printable {

    void print();

    default void printHeader() {
        System.out.println("Document");
    }
}

default методите позволяват добавяне на ново поведение към интерфейс, без всеки съществуващ клас задължително да бъде променян.

Статични методи в интерфейс

Интерфейс може да съдържа static методи. Те принадлежат на самия интерфейс и се извикват чрез неговото име.

interface TextUtils {

    static boolean isEmpty(String text) {
        return text == null || text.isEmpty();
    }
}

Извикване:

boolean result = TextUtils.isEmpty("");

Статичният метод в интерфейс не се извиква чрез обект от клас, който имплементира интерфейса.

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

Един интерфейс може да наследява друг интерфейс чрез ключовата дума extends.

interface Readable {

    void read();
}

interface Editable extends Readable {

    void edit();
}

Клас, който имплементира Editable, трябва да реализира както edit(), така и наследения метод read().

class Document implements Editable {

    @Override
    public void read() {
        System.out.println("Reading");
    }

    @Override
    public void edit() {
        System.out.println("Editing");
    }
}

Обикновен интерфейс

Обикновен интерфейс описва набор от действия, които могат да бъдат реализирани от различни класове. Той може да съдържа един или повече абстрактни методи.

interface Drawable {

    void draw();
}

class Circle implements Drawable {

    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
}

class Rectangle implements Drawable {

    @Override
    public void draw() {
        System.out.println("Drawing rectangle");
    }
}

И двата класа имплементират Drawable, затова могат да бъдат обработвани чрез общия тип Drawable.

Drawable shape = new Circle();
shape.draw();

Функционален интерфейс

Функционален интерфейс е интерфейс с точно един абстрактен метод. Той описва една операция, която трябва да бъде реализирана от клас или от друг механизъм за предоставяне на поведение.

@FunctionalInterface
interface Operation {

    int apply(int first, int second);
}

Анотацията @FunctionalInterface не е задължителна, но позволява на компилатора да провери дали интерфейсът остава функционален.

Маркиращ интерфейс

Маркиращ интерфейс е интерфейс без методи. Той се използва за означаване, че даден клас има определено свойство или може да бъде обработван по специален начин.

interface Auditable {

}

class Payment implements Auditable {

}

В примера Payment е маркиран като тип, който може да участва в логика за одит.

Особености при работа с интерфейси

От интерфейс не може да се създаде директен обект.

// Printable printable = new Printable(); // не се компилира

Интерфейсът може да се използва като тип, но реалният обект трябва да бъде от клас, който го имплементира.

Printable printable = new Document();

Един клас може да имплементира няколко интерфейса, а един интерфейс може да наследява друг интерфейс. Полетата, декларирани в интерфейс, са константи и се третират като public static final. Абстрактните методи в интерфейс трябва да бъдат реализирани от конкретния клас.

Абстрактен клас и интерфейс

Абстрактният клас се използва, когато класовете наследници имат обща основа, общо състояние или обща частична реализация. Интерфейсът се използва, когато трябва да се опише поведение, което може да бъде реализирано от несвързани класове.

abstract class Animal {

    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract String sound();
}

interface Trainable {

    void train();
}

class Dog extends Animal implements Trainable {

    public Dog(String name) {
        super(name);
    }

    @Override
    public String sound() {
        return "Bark";
    }

    @Override
    public void train() {
        System.out.println("Training dog");
    }
}

Класът Dog наследява общото състояние и поведение от Animal и едновременно с това имплементира способността Trainable.

Избор между абстрактен клас и интерфейс

Абстрактен клас се използва, когато има обща йерархия от тип „е“. Интерфейс се използва, когато различни класове трябва да поддържат еднакво действие, без непременно да имат общ родителски клас.

Например Dog е Animal, затова наследяването от абстрактен клас е подходящо. Document, Image и Report могат да бъдат Printable, но не е необходимо да наследяват един и същ родителски клас.

Абстрактен клас Интерфейс
Използва се, когато класовете споделят общо състояние и част от поведението си Използва се, когато различни класове трябва да предоставят еднакво поведение
Може да съдържа полета със състояние Декларираните полета са константи
Един клас може да наследи само един абстрактен клас Един клас може да имплементира множество интерфейси
Може да съдържа конструктори, абстрактни методи и обикновени методи Може да съдържа константи, абстрактни методи, default методи и static методи
Подходящ е при наличие на обща частична реализация Подходящ е за описание на роля или способност

This site uses Just the Docs, a documentation theme for Jekyll.