Menu
Coddy logo textTech

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 Truck

Klasy 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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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, SMSNotification i PushNotification. Twoja zapieczętowana klasa potrzebuje chronionego pola message (String), konstruktora do jego inicjalizacji, metody pobierającej wiadomość oraz metody abstrakcyjnej deliver(), 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 pole recipient (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(). Metoda deliver() 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 pole phoneNumber (String). Jej konstruktor przyjmuje wiadomość i numer telefonu. Metoda deliver() powinna zwracać: Sending SMS to [phoneNumber]: [message]
  • PushNotification.java: Utwórz klasę final, która rozszerza Notification. PushNotification ma dodatkowe prywatne pole deviceId (String). Jej konstruktor przyjmuje wiadomość i identyfikator urządzenia. Metoda deliver() 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łania deliver() 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
        
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Java online