sealed, permits și non-sealed în Java

O clasă sau interfață sealed își alege explicit subtipurile (permits); fiecare subtip este final, sealed sau non-sealed.

Sintaxă
sealed interface Forma permits Cerc, Patrat, Poligon { }

record Cerc(double raza) implements Forma { }           // record: implicit final
final class Patrat implements Forma { ... }
non-sealed class Poligon implements Forma { ... }    // deschisă din nou

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.