Klasy zapieczętowane (Java 17+)
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 52 z 87.
W tradycyjnej Javie, gdy tworzysz klasę, każda inna klasa może ją rozszerzać (chyba że oznaczysz ją jako final). Ale co, jeśli chcesz zezwolić na dziedziczenie, ale nie od każdego? Java 17 wprowadziła klasy zapieczętowane, aby zapewnić ci precyzyjną kontrolę nad tym, które klasy mogą rozszerzać twoją klasę.
Klasa sealed jawnie deklaruje swoje dozwolone podklasy za pomocą słów kluczowych sealed i permits:
sealed abstract class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}
final class Circle extends Shape {
double radius;
double area() { return Math.PI * radius * radius; }
}
final class Rectangle extends Shape {
double width, height;
double area() { return width * height; }
}
final class Triangle extends Shape {
double base, height;
double area() { return 0.5 * base * height; }
}Tylko Circle, Rectangle i Triangle mogą rozszerzać Shape. Każda inna klasa próbująca go rozszerzyć spowoduje błąd kompilacji.
Każda dozwolona podklasa musi używać jednego z trzech modyfikatorów: final (bez dalszego rozszerzania), sealed (kontynuuje łańcuch ograniczeń) lub non-sealed (umożliwia nieograniczone rozszerzanie):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Nie można po niej dziedziczyć
non-sealed class Truck extends Vehicle { } // Każdy może dziedziczyć po TruckKlasy zapieczętowane są szczególnie przydatne w połączeniu z dopasowywaniem do wzorca w wyrażeniach switch, ponieważ kompilator zna wszystkie możliwe podtypy i może sprawdzić, czy uwzględniono wszystkie przypadki. Są idealne do modelowania ustalonego zbioru typów w danej dziedzinie, takich jak metody płatności, typy odpowiedzi lub figury geometryczne.
Wyzwanie
ŁatwyZbudujmy system powiadomień, który demonstruje klasy zapieczętowane, ograniczając rodzaje powiadomień, jakie mogą istnieć w Twojej aplikacji. Utworzysz hierarchię, w której dozwolone są tylko określone rodzaje powiadomień, a każdy z nich obsługuje dostarczanie w inny sposób.
Rozmieścisz swój kod w czterech plikach:
Notification.java: Utwórz zapieczętowaną klasę abstrakcyjną, która będzie klasą bazową dla wszystkich powiadomień. Powinna zezwalać dokładnie na trzy podklasy:EmailNotification,SMSNotificationiPushNotification. Twoja zapieczętowana klasa potrzebuje chronionego polamessage(String), konstruktora do jego inicjalizacji, metody pobierającej wiadomość oraz metody abstrakcyjnejdeliver(), która zwraca String opisujący sposób wysłania powiadomienia.EmailNotification.java: Utwórz klasęfinal, która rozszerza Notification. EmailNotification ma dodatkowe prywatne polerecipient(String) przechowujące adres e-mail. Jej konstruktor powinien przyjmować zarówno wiadomość, jak i odbiorcę, wywołując konstruktor klasy nadrzędnej za pomocąsuper(). Metodadeliver()powinna zwracać:Sending email to [recipient]: [message]SMSNotification.java: Utwórz klasęnon-sealed, która rozszerza Notification. Dzięki temu inne klasy będą mogły ją rozszerzać w przyszłości, jeśli zajdzie taka potrzeba. SMSNotification ma dodatkowe prywatne polephoneNumber(String). Jej konstruktor przyjmuje wiadomość i numer telefonu. Metodadeliver()powinna zwracać:Sending SMS to [phoneNumber]: [message]PushNotification.java: Utwórz klasęfinal, która rozszerza Notification. PushNotification ma dodatkowe prywatne poledeviceId(String). Jej konstruktor przyjmuje wiadomość i identyfikator urządzenia. Metodadeliver()powinna zwracać:Sending push to device [deviceId]: [message]Main.java: Połącz elementy swojego systemu powiadomień! Otrzymasz cztery dane wejściowe: wiadomość (String), adres e-mail (String), numer telefonu (String) i identyfikator urządzenia (String).Utwórz po jednym powiadomieniu każdego rodzaju, używając tej samej wiadomości, ale odpowiednich danych kontaktowych. Zapisz wszystkie trzy w tablicy typu
Notification[], aby zademonstrować polimorfizm za pomocą klas zapieczętowanych. Następnie przejdź przez tablicę i wyświetl wynik wywołaniadeliver()dla każdego powiadomienia.
Otrzymasz cztery dane wejściowe w następującej kolejności: wiadomość, adres e-mail, numer telefonu i identyfikator urządzenia.
Zwróć uwagę, jak zapieczętowana klasa ogranicza hierarchię: tylko trzy dozwolone klasy mogą rozszerzać Notification. Każda podklasa musi zadeklarować się jako final, sealed lub non-sealed. Daje Ci to pełną kontrolę nad hierarchią typów, a jednocześnie umożliwia polimorfizm!
Spróbuj swoich sił
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Odczytaj cztery wartości wejściowe
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: Utwórz po jednej instancji każdego typu powiadomienia, używając tej samej wiadomości
// - EmailNotification z message i email
// - SMSNotification z message i phoneNumber
// - PushNotification z message i deviceId
// TODO: Umieść wszystkie trzy powiadomienia w tablicy Notification[]
// To pokazuje polimorfizm z użyciem klas zapieczętowanych
// TODO: Przejdź przez tablicę i wypisz wynik deliver()
// dla każdego powiadomienia
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatnościPoćwicz samodzielnie: Kompilator Java online