CodeGym /Corsi /JAVA 25 SELF /Pattern Matching nello switch (Java 17/21+)

Pattern Matching nello switch (Java 17/21+)

JAVA 25 SELF
Livello 65, Lezione 2
Disponibile

1. Lo switch classico: i limiti del passato

Ricordiamo com'era il switch in Java prima del pattern matching. Era, diciamolo francamente, piuttosto conservatore. Funzionava solo con i primitivi (int, char, e un po' più tardi enum, String), e se volevate gestire diversi tipi di oggetti — bisognava scrivere lunghe catene di if-else.

Object obj = ...;
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println("Stringa: " + s);
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    System.out.println("Numero: " + i);
} else if (obj == null) {
    System.out.println("null!");
} else {
    System.out.println("Qualcos'altro");
}

Comodo? Non troppo. Il codice è verboso, è facile sbagliare e, se si aggiunge un nuovo tipo — bisogna di nuovo riscrivere la catena di if-else.

2. Pattern Matching nello switch: una rivoluzione in Java 17/21+

Java 17 (in anteprima) e definitivamente in Java 21+ hanno introdotto una nuova versione di switch, che supporta il pattern matching. Ora switch può lavorare non solo con i primitivi, ma anche con oggetti di qualsiasi tipo, persino con null, e supporta condizioni aggiuntive (espressioni di guardia).

Nuova sintassi: concisione e potenza

Esempio di semplice pattern matching in switch:

Object obj = ...;
switch (obj) {
    case String s -> System.out.println("Stringa: " + s);
    case Integer i -> System.out.println("Numero: " + i);
    case null -> System.out.println("null!");
    default -> System.out.println("Qualcos'altro");
}

Che cosa succede qui?

  • Se obj è una stringa, la variabile s viene automaticamente convertita al tipo String.
  • Se obj è un intero, la variabile i diventa Integer.
  • Se obj è null, verrà eseguito il ramo dedicato.
  • Tutto il resto — default.

Perché è comodo?

  • Nessun cast manuale: Java si occupa lei della sicurezza dei tipi.
  • Meno codice: niente parentesi superflue, cast e duplicazioni.
  • Più sicuro: il compilatore verifica che non vi siate dimenticati di gestire le alternative possibili (soprattutto con le classi sealed, di cui parleremo tra poco).
  • È facile aggiungere nuovi tipi: basta aggiungere un altro ramo case.

3. Pattern Matching nello switch: esempi di base

Esempio 1: gestione di tipi diversi

Object obj = 42;

switch (obj) {
    case String s -> System.out.println("Stringa: " + s);
    case Integer i -> System.out.println("Numero: " + i);
    case null -> System.out.println("È null");
    default -> System.out.println("Tipo sconosciuto");
}

Output:

Numero: 42

Esempio 2: uso delle condizioni di guardia (when)

Talvolta non basta conoscere il tipo — si vuole aggiungere una condizione aggiuntiva.

Object obj = "Hello, world!";

switch (obj) {
    case String s when s.length() > 10 -> System.out.println("Stringa lunga: " + s);
    case String s -> System.out.println("Stringa corta: " + s);
    case Integer i when i > 0 -> System.out.println("Numero positivo: " + i);
    case Integer i -> System.out.println("Numero non positivo: " + i);
    default -> System.out.println("Qualcos'altro");
}

Output:

Stringa lunga: Hello, world!

Da notare:

  • when è un'espressione di guardia, un controllo aggiuntivo per case.
  • Se la condizione non è soddisfatta, vengono valutati i case successivi.

Esempio 3: gestione di null

Object obj = null;

switch (obj) {
    case null -> System.out.println("È null!");
    default -> System.out.println("Non null");
}

Output:

È null!

4. Pattern matching nello switch con classi ed ereditarietà

Il pattern matching in switch funziona particolarmente bene con le gerarchie di classi.

sealed interface Shape permits Circle, Rectangle, Square {}

final class Circle implements Shape {
    final double radius;
    Circle(double radius) { this.radius = radius; }
}

final class Rectangle implements Shape {
    final double width, height;
    Rectangle(double width, double height) { this.width = width; this.height = height; }
}

final class Square implements Shape {
    final double side;
    Square(double side) { this.side = side; }
}
Shape shape = new Rectangle(2, 3);

String description = switch (shape) {
    case Circle c -> "Cerchio con raggio " + c.radius;
    case Rectangle r -> "Rettangolo " + r.width + "x" + r.height;
    case Square s -> "Quadrato con lato " + s.side;
};
System.out.println(description);

Output:

Rettangolo 2.0x3.0

Nota: Se utilizzate sealed-classi e avete gestito tutte le loro varianti, il ramo default non è necessario — il compilatore capisce che non possono esserci altri casi!

5. Uso di switch con classi record e pattern annidati

Il pattern matching in switch supporta il lavoro con le classi record e i pattern annidati (a partire da Java 21+).

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

String result = switch (obj) {
    case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
        "Linea da (" + x1 + "," + y1 + ") a (" + x2 + "," + y2 + ")";
    case Point(int x, int y) -> "Punto (" + x + "," + y + ")";
    default -> "Oggetto sconosciuto";
};
System.out.println(result);

Output:

Linea da (1,2) a (3,4)

6. Caratteristiche e limitazioni del pattern matching nello switch

Le variabili sono visibili solo all'interno del case

La variabile dichiarata nel pattern (ad esempio String s) esiste solo all'interno del blocco case corrispondente. Al di fuori non è disponibile.

switch (obj) {
    case String s -> System.out.println(s); // qui s è visibile
    // System.out.println(s); // qui errore di compilazione!
}

Gestione di null

  • Nel vecchio switch sugli oggetti, passare null portava a una NullPointerException.
  • Nel nuovo switch con pattern matching si può gestire esplicitamente null tramite case null.

Espressioni di guardia (when)

Le condizioni aggiuntive (when) sono disponibili solo nello switch in stile expression (sintassi a freccia ->).

case String s when s.isEmpty() -> System.out.println("Stringa vuota");

Requisiti per JDK e IDE

  • Il pattern matching in switch è una funzionalità di Java 17 (preview) e Java 21+ (stabile).
  • Per usarlo servono JDK 21 e un'IDE che supporti le nuove funzionalità (ad esempio, IntelliJ IDEA 2023+).

7. Applicare lo switch con pattern matching in un'app reale

Consideriamo un'app didattica — un semplice task tracker. Supponiamo di avere un tipo astratto di attività e diversi suoi tipi:

sealed interface Task permits SimpleTask, DeadlineTask, MeetingTask {}

final class SimpleTask implements Task {
    final String description;
    SimpleTask(String description) { this.description = description; }
}

final class DeadlineTask implements Task {
    final String description;
    final java.time.LocalDate deadline;
    DeadlineTask(String description, java.time.LocalDate deadline) {
        this.description = description;
        this.deadline = deadline;
    }
}

final class MeetingTask implements Task {
    final String topic;
    final java.time.LocalTime time;
    MeetingTask(String topic, java.time.LocalTime time) {
        this.topic = topic;
        this.time = time;
    }
}

Tutta la logica in un unico switch compatto:

Task task = new DeadlineTask("Consegnare il progetto", java.time.LocalDate.of(2024, 7, 1));

String info = switch (task) {
    case SimpleTask st -> "Attività semplice: " + st.description;
    case DeadlineTask dt -> "Attività con deadline: " + dt.description + ", entro " + dt.deadline;
    case MeetingTask mt -> "Riunione: " + mt.topic + " alle " + mt.time;
};
System.out.println(info);

Output:

Attività con deadline: Consegnare il progetto, entro 2024-07-01

8. Errori tipici nell'uso del pattern matching nello switch

Errore n°1: ci si dimentica di gestire null. Se non aggiungete case null, e obj può essere null — otterrete una MatchException (o una NullPointerException negli switch vecchi). Gestite sempre esplicitamente null, quando possibile.

Errore n°2: non si considerano tutti i discendenti di una classe sealed. Se usate una classe sealed e non gestite tutte le varianti nello switch, il compilatore genererà un errore o richiederà un ramo default. Non dimenticate di aggiungere nuovi case quando estendete la gerarchia.

Errore n°3: la variabile del pattern non è visibile fuori dal blocco case. La variabile dichiarata nel pattern esiste solo all'interno del blocco a freccia. Tentare di usarla fuori da quel blocco causerà un errore di compilazione.

Errore n°4: usare le nuove funzionalità su una JDK o un'IDE vecchia. Il pattern matching in switch richiede Java 21+ e il supporto appropriato nella vostra IDE. Se usate una versione vecchia — otterrete un errore di compilazione o suggerimenti non corretti.

Errore n°5: confusione con le espressioni di guardia. State cercando di usare when in uno switch a blocchi tradizionale? Non funziona. Le guardie sono disponibili solo per la sintassi a freccia ->.

Errore n°6: dimenticare il ramo default quando si lavora con gerarchie aperte. Se il vostro switch non lavora esclusivamente con sealed-classi, aggiungete sempre default — altrimenti non tutte le alternative verranno gestite.

1
Compito
JAVA 25 SELF, livello 65, lezione 2
Bloccato
Classificatore di numeri per il sistema di analisi 🔢
Classificatore di numeri per il sistema di analisi 🔢
1
Compito
JAVA 25 SELF, livello 65, lezione 2
Bloccato
Sistema intelligente di identificazione degli animali per una clinica veterinaria 🐕🐈🦜
Sistema intelligente di identificazione degli animali per una clinica veterinaria 🐕🐈🦜
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION