Ce face sealed, permits și non-sealed
Normal, oricine poate extinde o clasă sau implementa o interfață. Cu sealed spui exact cine are voie: sealed interface Forma permits Cerc, Patrat. Dacă toate subtipurile sunt în același fișier, permits poate lipsi: compilatorul le găsește singur.
Fiecare subtip permis trebuie să aleagă: final (nu mai poate fi extins; record-urile și enum-urile sunt deja finale), sealed (își alege la rândul lui subtipurile) sau non-sealed (redevine deschis pentru oricine).
Câștigul principal: compilatorul cunoaște toate variantele. Un switch cu pattern matching care tratează fiecare subtip este complet fără default, iar dacă adaugi o variantă nouă, toate switch-urile incomplete devin erori de compilare. Combinația sealed interface + record modelează date de tipul „este fie A, fie B, fie C”.
Exemple
Exemplul 1: Ierarhie închisă și switch fără default
Forma are exact trei variante. Switch-ul tratează toate trei, deci nu are nevoie de default.
sealed interface Forma permits Cerc, Dreptunghi, Triunghi { }
record Cerc(double raza) implements Forma { }
record Dreptunghi(double latime, double inaltime) implements Forma { }
record Triunghi(double baza, double inaltime) implements Forma { }
public class Main {
static double arie(Forma f) {
return switch (f) {
case Cerc c -> Math.PI * c.raza() * c.raza();
case Dreptunghi d -> d.latime() * d.inaltime();
case Triunghi t -> t.baza() * t.inaltime() / 2;
};
}
public static void main(String[] args) {
Forma[] forme = {new Cerc(2), new Dreptunghi(3, 5), new Triunghi(4, 3)};
for (Forma f : forme) {
System.out.printf("%s -> %.2f%n", f, arie(f));
}
}
}
Rezultat
Cerc[raza=2.0] -> 12.57
Dreptunghi[latime=3.0, inaltime=5.0] -> 15.00
Triunghi[baza=4.0, inaltime=3.0] -> 6.00
Exemplul 2: final, sealed și non-sealed în aceeași ierarhie
Card este finală, Transfer este la rândul ei sigilată (doar TransferSepa), iar AltaPlata este non-sealed, deci PlataCrypto o poate extinde liber. permits lipsește, pentru că totul e în același fișier.
sealed abstract class Plata {
abstract double suma();
}
final class Card extends Plata {
@Override double suma() { return 120.0; }
}
sealed class Transfer extends Plata {
@Override double suma() { return 900.0; }
}
final class TransferSepa extends Transfer {
@Override double suma() { return 1500.0; }
}
non-sealed class AltaPlata extends Plata {
@Override double suma() { return 0; }
}
class PlataCrypto extends AltaPlata {
@Override double suma() { return 75.5; }
}
public class Main {
static String tip(Plata p) {
return switch (p) {
case Card c -> "card";
case TransferSepa t -> "transfer SEPA";
case Transfer t -> "transfer";
case AltaPlata a -> "altă plată (" + a.getClass().getSimpleName() + ")";
};
}
public static void main(String[] args) {
Plata[] plati = {new Card(), new Transfer(), new TransferSepa(), new PlataCrypto()};
for (Plata p : plati) {
System.out.println(tip(p) + ": " + p.suma() + " lei");
}
System.out.println("sigilată? " + Plata.class.isSealed());
System.out.println("subtipuri permise: " + Plata.class.getPermittedSubclasses().length);
}
}
Rezultat
card: 120.0 lei
transfer: 900.0 lei
transfer SEPA: 1500.0 lei
altă plată (PlataCrypto): 75.5 lei
sigilată? true
subtipuri permise: 3
Greșeli frecvente
- Uiți modificatorul pe un subtip:
sealed, non-sealed or final modifiers expected. Fiecare clasă permisă trebuie să aleagă unul. - O clasă din afara listei încearcă să extindă tipul sigilat:
class is not allowed to extend sealed class: Plata. - Pui
defaultîntr-un switch pe un tip sigilat. Merge, dar pierzi verificarea: când adaugi o variantă, compilatorul nu te mai anunță.
Comenzi înrudite
Pattern matching (instanceof și switch): Pattern matching testează tipul (și forma) unui obiect și îi extrage părțile într-un singur pas, în instanceof sau în switch.record: record declară o clasă imutabilă pentru date: constructorul, accesorii, equals, hashCode și toString sunt generați automat.Interfețe (interface, implements): O interfață (interface) descrie ce metode trebuie să aibă o clasă; clasa o promite cu implements și poate implementa mai multe interfețe.final (și static final): final interzice schimbarea: o variabilă primește valoare o singură dată, o metodă nu poate fi rescrisă, o clasă nu poate fi extinsă.switch expressions și yield: switch folosit ca expresie care produce o valoare; yield returnează valoarea dintr-o ramură cu bloc.