W3docs

Java throw e throws

Lancia eccezioni manualmente in Java con throw e dichiarale nelle firme dei metodi con throws.

Finora abbiamo catturato eccezioni lanciate da altro codice. Ora vedrai come lanciarne di tue. Due parole chiave svolgono la maggior parte del lavoro — ed è facile confonderle perché differiscono di una sola lettera.

  • throw — un'istruzione che solleva un'eccezione a runtime. Una parola, nel codice che viene eseguito.
  • throws — una dichiarazione nella firma di un metodo che indica "questo metodo potrebbe lanciare questi tipi di eccezione." Viene verificata dal compilatore, non viene mai eseguita.

throw accade. throws avverte. Tieni a mente questa coppia.

Lanciare un'eccezione

throw prende un'espressione di tipo Throwable (o qualsiasi suo sottotipo) e la solleva. Il metodo corrente esce immediatamente, lo stack inizia a srotolarsi e l'eccezione comincia la ricerca di un catch corrispondente.

if (amount < 0) {
  throw new IllegalArgumentException("amount must be non-negative, got " + amount);
}

Tre dettagli:

  • Puoi lanciare solo un Throwable. Il compilatore lo impone — throw "oops"; non compila.
  • Lanci sempre un'istanza, non una classe. throw new X(...), mai throw X.
  • L'istanza può essere una costruita inline (comune), oppure un oggetto preesistente (raro — le eccezioni portano stack trace dalla loro costruzione, quindi riutilizzarne una congela il trace sbagliato).

Quando lanciare

Lancia un'eccezione quando il metodo corrente non riesce a rispettare il suo contratto. Alcuni casi chiari:

  • Argomenti non validiIllegalArgumentException per "mi hai chiamato in modo errato."
  • Stato erratoIllegalStateException per "mi hai chiamato nel momento sbagliato" (es. next() su un iteratore vuoto).
  • Dati mancanti — eccezioni specifiche del dominio come UserNotFoundException.
  • Operazioni esterne fallite — errori IO, errori di rete. Di solito queste arrivano dalla chiamata appena effettuata, quindi non le costruisci tu stesso; le lasci propagare o le incapsula in un'eccezione di livello più alto.

Il caso in cui non dovresti lanciare: come scorciatoia per il flusso di controllo con risultati normali. "Lanciare per il flusso di controllo" è lento e confuso. Se "non trovato" è un risultato ordinario, restituisci un Optional<T>, non una NotFoundException.

Scegliere un tipo

Le eccezioni integrate in java.lang coprono la maggior parte dei casi senza cerimonie:

  • IllegalArgumentException — argomento non valido
  • IllegalStateException — stato errato
  • NullPointerException — un argomento richiesto era null (usa Objects.requireNonNull)
  • UnsupportedOperationException — operazione non implementata (es. add di una lista immutabile)
  • ArithmeticException — errore matematico

Quando il fallimento è specifico del tuo dominio — "utente non trovato," "coupon non valido," "configurazione non sincronizzata" — scrivi una piccola classe personalizzata per esso. Tra due capitoli lo faremo esattamente.

La clausola throws

Se il tuo metodo potrebbe lanciare un'eccezione checked che non cattura internamente, devi dichiararla:

public Config loadConfig(Path p) throws IOException, ParseException {
  String text = Files.readString(p);
  return parser.parse(text);
}

La clausola fa parte del contratto del metodo. Dice a ogni chiamante: "se mi chiami, devi o catturare queste eccezioni o dichiararle a tua volta." Il compilatore lo impone — è questo che le rende checked.

Alcune regole:

  • Dichiari solo eccezioni checked. Le RuntimeException e le loro sottoclassi sono unchecked — dichiararle è consentito ma non obbligatorio, e di solito non lo si fa.
  • Puoi dichiarare più tipi di quanti ne lanci effettivamente — utile quando vuoi tenere aperta l'opzione per implementazioni future, anche se è un leggero rumore.
  • Un metodo che sovrascrive un altro può dichiarare le stesse o meno eccezioni checked del genitore (e solo sottotipi di quelle dichiarate). Non può aggiungerne di nuove. Questo è il principio di sostituzione di Liskov applicato alle eccezioni.

throw e throws insieme

Un metodo reale di solito fa entrambe le cose:

public User loadUser(String id) throws IOException {
  if (id == null || id.isBlank()) {
    throw new IllegalArgumentException("id must be non-blank");
  }
  String json = httpClient.get("/users/" + id);   // may throw IOException
  return parser.toUser(json);
}
  • Il throws IOException dichiara l'eccezione checked che può provenire da httpClient.get.
  • Il throw new IllegalArgumentException(...) solleva una unchecked per input non valido. Non deve comparire nella clausola throws.

Incapsulare un'eccezione

Quando un'eccezione di basso livello non è significativa nel tuo layer, incapsulala in una che lo sia. Passa l'originale come causa in modo che il trace rimanga intatto:

try {
  return Files.readString(configPath);
} catch (IOException e) {
  throw new ConfigLoadException("could not load config from " + configPath, e);
}

Il pattern del costruttore con secondo argomento — (message, cause) — è standard in Exception, IOException e tutti i built-in. Quando scrivi la tua classe di eccezione personalizzata, fornisci entrambi i costruttori.

Un esempio pratico

Un piccolo helper in stile bancario che valida l'input con IllegalArgumentException, segnala un account vuoto con IllegalStateException, e lascia risalire al chiamante un'eccezione checked tramite throws. Il driver mostra come appare ognuna quando viene lanciata.

java— editable, runs on the server

I tre casi runtime — argomento, stato e prelievo riuscito — passano attraverso un unico catch. Il quarto, archive(), compila solo perché main può catturare Exception e perché archive() ha dichiarato throws ArchiveException. Prova a rimuovere la clausola throws e il programma non compilerà.

Cosa c'è dopo

Il compilatore tratta alcune eccezioni in modo rigoroso (devi gestirle) e altre in modo permissivo (non è obbligatorio). Questa distinzione è l'argomento del prossimo capitolo. Continua su Eccezioni checked vs. unchecked in Java.

Esercizio

Pratica
La firma di un metodo recita `public void save() throws IOException`. Il corpo del metodo è vuoto — non lancia nulla. Compilerà?
La firma di un metodo recita `public void save() throws IOException`. Il corpo del metodo è vuoto — non lancia nulla. Compilerà?
Was this page helpful?