Klasa abstrakcyjna vs. Interfejs w Javie: wyjaśnienie z przykładami

W Javie, abstrakcja jest realizowana za pomocą klas abstrakcyjnych oraz interfejsów. Abstrakcja w programowaniu obiektowym polega na ukrywaniu szczegółów implementacyjnych przed użytkownikami końcowymi.

Dzięki abstrakcji, użytkownik ma świadomość dostępnych funkcjonalności, lecz nie wnika w ich wewnętrzne działanie.

Przeanalizujmy te dwa mechanizmy, aby lepiej zrozumieć ich zastosowanie.

Klasa Abstrakcyjna

Klasa abstrakcyjna w Javie to taka, której nie można bezpośrednio instancjonować (tworzyć obiektów). Może ona zawierać metody abstrakcyjne, jak i zwykłe. Metoda abstrakcyjna to taka, która ma zdefiniowaną sygnaturę (nazwę, parametry, zwracany typ), ale nie posiada ciała (implementacji).

Przykładem może być klasa abstrakcyjna `GraphicObject` z dokumentacji Oracle.

Klasę abstrakcyjną tworzymy, poprzedzając słowo kluczowe `class` słowem `abstract`.

abstract class AbstractClass {
    void run() {
        System.out.println("uruchomiono");
    }
}

Klasy abstrakcyjne można rozszerzać. Oznacza to, że inne klasy mogą dziedziczyć po klasie abstrakcyjnej.

abstract class AbstractClass {
    void run() {
        System.out.println("uruchomiono");
    }
}

class ExtendingAbstractClass extends AbstractClass {
    void newMethod() {
        System.out.println("nowa metoda");
    }

    @Override
    void run() {
        System.out.println("przesłonięto");
    }
}

Klasy abstrakcyjne są przydatne, gdy chcemy zaimplementować wspólne metody dla wielu klas dziedziczących. Dodatkowo, definiowanie metod abstrakcyjnych umożliwia określenie wspólnych interfejsów z różnymi implementacjami. Rozważmy przykład.

Załóżmy, że mamy samochód, który posiada funkcje takie jak uruchamianie, zatrzymywanie i cofanie. Są to funkcje wspólne dla wszystkich samochodów.

Jednak funkcje automatyczne, jak np. jazda autonomiczna, mogą różnić się w zależności od rodzaju samochodu. Zobaczmy, jak to zaimplementować obiektowo.

Najpierw utwórzmy abstrakcyjną klasę `Car`, po której będą dziedziczyć różne typy samochodów.

abstract class Car {
    void start() {
        // implementation
        System.out.println("samochód uruchomiony");
    }

    void stop() {
        // implementation
        System.out.println("silnik zatrzymany");
    }

    void reverse() {
        // implementation
        System.out.println("włączono tryb cofania");
    }

    abstract void selfDrive();
}

Metody `start()`, `stop()` i `reverse()` są wspólne dla wszystkich samochodów i mają zdefiniowaną implementację. Metoda `selfDrive()` (jazda autonomiczna) jest abstrakcyjna, ponieważ jej implementacja zależy od typu samochodu.

class CarTypeA extends Car {
    @Override
    void start() {
        super.start();
    }

    @Override
    void stop() {
        super.stop();
    }

    @Override
    void reverse() {
        super.reverse();
    }

    void selfDrive() {
        // custom implementation
        System.out.println("włączono tryb autonomicznej jazdy typu A");
    }
}
class CarTypeB extends Car {
    // ...podobne metody

    void selfDrive() {
        // custom implementation
        // inna implementacja niż w CarTypeB
        System.out.println("włączono tryb autonomicznej jazdy typu B");
    }
}

Ważne jest, aby pamiętać, że jeśli podklasa nie zaimplementuje wszystkich metod abstrakcyjnych z klasy abstrakcyjnej, sama również musi być zadeklarowana jako abstrakcyjna.

Interfejs

Interfejs to rodzaj kontraktu, który informuje klasę, jakie metody musi ona zaimplementować. W naszym przykładzie samochodu, posiada on podstawowe funkcje jak uruchamianie, poruszanie się i zatrzymywanie. Są one wspólne dla wszystkich samochodów.

Jeśli klasa implementuje interfejs `Car`, musi zaimplementować wszystkie zadeklarowane w nim metody, aby samochód mógł działać poprawnie i bezpiecznie.

Podobnie jak w przypadku klas abstrakcyjnych, nie można tworzyć obiektów interfejsu. Interfejs można traktować jako klasę w pełni abstrakcyjną, gdyż zawiera wyłącznie metody abstrakcyjne (bez implementacji).

Interfejs tworzymy za pomocą słowa kluczowego `interface`.

interface CAR {
    void start();
    void stop();
    void move();
}

Interfejs implementujemy za pomocą słowa kluczowego `implements` podczas definiowania klasy.

class CarTypeB implements CAR {
    public void start() {
        System.out.println("uruchomiono");
    }

    public void stop() {
        System.out.println("zatrzymano");
    }

    public void move() {
        System.out.println("jedzie");
    }
}

Podobieństwa

Jedyną wspólną cechą klas abstrakcyjnych i interfejsów jest to, że nie można tworzyć ich instancji.

Różnice

Cecha Klasa abstrakcyjna Interfejs
Dziedziczenie i implementacja Klasa może dziedziczyć tylko po jednej klasie abstrakcyjnej. Klasa może implementować wiele interfejsów.
Typy zmiennych Może posiadać zmienne `final`, nie-`final`, `static` i niestatyczne. Może posiadać jedynie zmienne `static` i `final`.
Typy metod Może zawierać zarówno metody abstrakcyjne, jak i nieabstrakcyjne. Może zawierać tylko metody abstrakcyjne (z wyjątkiem metod `static`).
Modyfikatory dostępu Klasa abstrakcyjna może mieć zdefiniowane modyfikatory dostępu. Sygnatury metod zdefiniowane w interfejsie są domyślnie publiczne. Interfejs nie ma modyfikatora dostępu.
Konstruktory i destruktory Może deklarować konstruktory i destruktory. Nie może deklarować konstruktorów ani destruktorów.
Szybkość Szybko Wolno

Kiedy Używać Klasy Abstrakcyjnej i Interfejsu?

Klasy abstrakcyjne stosuj gdy:

  • Chcesz udostępnić wspólne metody i pola dla wielu klas.
  • Chcesz modyfikować stan obiektów poprzez pola niestatyczne i niekońcowe.

Interfejsy stosuj gdy:

  • Chcesz zdefiniować zachowanie klasy bez wnikania w szczegóły implementacyjne.
  • Chcesz wymusić implementację określonych metod w klasach.

Podsumowanie

Interfejsy są często używane do tworzenia API, ponieważ definiują strukturę funkcjonalności bez narzucania konkretnej implementacji.

Klasy abstrakcyjne stosuje się, aby współdzielić wspólne metody abstrakcyjne i nieabstrakcyjne między wieloma klasami dziedziczącymi, co czyni kod bardziej modularnym i użytecznym.

Aby poszerzyć wiedzę o Javie, skorzystaj z dostępnych kursów online. Przygotowujesz się do rozmowy kwalifikacyjnej z Javy? Sprawdź przykładowe pytania dotyczące programowania obiektowego.