Trovo davvero strano che il metodo initCause
della classe Throwable
di Java possa essere chiamato una volta sola, o addirittura nulla (se è stato utilizzato il costruttore che accetta Throwable
). Ciò rende il concatenamento delle eccezioni non facile come penso dovrebbe essere.
private Throwable cause = this;
public Throwable(String message, Throwable cause) {
...
this.cause = cause;
}
public Throwable getCause() {
return (cause==this ? null : cause);
}
public synchronized Throwable initCause(Throwable cause) {
if (this.cause != this)
throw new IllegalStateException("Can't overwrite cause");
if (cause == this)
throw new IllegalArgumentException("Self-causation not permitted");
this.cause = cause;
return this;
}
Porta a un brutto codice boilerplate che deve occuparsi dei casi limite in cui il metodo potrebbe essere già stato chiamato, ma la parte del codice dove vuoi chiamarla di nuovo, sei non sono sicuro.
Ciò che è peggio è che il metodo getCause
non ti dice correttamente se il metodo initCause
è stato chiamato o meno. Questo perché qualcun altro potrebbe aver chiamato initCause(null)
e questo renderebbe getCause
per restituire null
mentre allo stesso tempo ha initCause
fallisce con IllegalStateException
(perché null != this
è true).
Il risultato è un codice boilerplate come segue:
try {
exp1.initCause(exp2);
} catch (IllegalStateException ise) {
// do something or ignore
} catch (IllegalArgumentException iae) {
// do something or ignore
}
Mentre dovrei essere in grado di chiamare semplicemente exp1.initCause(exp2);
senza preoccuparti se è la stessa eccezione o se il metodo era già stato chiamato (e lasciare che il metodo si occupi di cosa fare se una delle condizioni fosse vera; per esempio semplicemente ignora la nuova causa).
Qual è la ragione di questo strano progetto?
Modifica:
Vorrei chiarire che non ho alcun problema con la causa che è immutabile. Il mio problema è che, assicurando che tale immutabilità non sia violata, è un peso per i programmatori, piuttosto che essere gestito automaticamente dall'implementazione.
Avrebbe avuto più senso per me se la causa fosse implementata come segue:
private Throwable cause; // null means no cause
public Throwable(String message, Throwable cause) {
...
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
public synchronized Throwable initCause(Throwable cause) {
if (this.cause == null)
this.cause = cause;
return this;
}
O anche se è stata mantenuta l'implementazione originale , sarebbe stato possibile aggiungere un pratico metodo:
public boolean hasCause() {
return (this.cause != this);
}
In modo che potessi ridurre il codice boilerplate semplicemente:
if (!exp1.hasCause()) exp1.initCause(exp2);
Vorrei anche chiarire che questa non è una domanda su quale versione di Java dovrei usare, o se dovrei continuare ad usare versioni di Java che hanno raggiunto il termine del loro ciclo di vita. Esistono molte ragioni per cui le versioni precedenti di Java continuano ad essere utilizzate diverse da "lazy to upgrade" o "non interessa la sicurezza" . :)