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.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.