Perché la initCause lanciabile è progettata per essere chiamata una sola volta?

2

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" . :)

    
posta ADTC 29.08.2014 - 05:41
fonte

1 risposta

3

What is the reason behind such an odd design?

Non è un disegno strano. La causa dovrebbe essere immutabile dal momento che il codice utente che gioca con la causa molto probabilmente causerà più danni che benefici e interromperà la semantica della causa.

La spiegazione dell'esistenza del metodo initCause può essere trovata in Throwable javadoc:

Because the initCause method is public, it allows a cause to be associated with any throwable, even a "legacy throwable" whose implementation predates the addition of the exception chaining mechanism to Throwable.

In effetti, il concatenamento delle eccezioni è stato aggiunto solo in Java 1.4. Prima di ciò, un'eccezione non aveva una causa.

Hanno dovuto aggiungere questo metodo per motivi di compatibilità. Potresti ancora utilizzare una vecchia eccezione senza il parametro di causa nel costruttore ma continui a impostare la causa in seguito.

Per quanto riguarda il tuo caso d'uso, quello che vuoi usare è eccezioni soppresse . Sono stati aggiunti in Java 7 per gestire il tuo caso d'uso esatto (vedi la documentazione try-with-catch per esempio).

Se si utilizza una versione di Java obsoleta, è possibile aggiungere un meccanismo simile alla propria eccezione (o eseguire l'aggiornamento a una versione non di fine vita).

Se vuoi davvero rompere l'eccezione concatenando semantica puoi sempre creare una nuova eccezione con una causa composita / compromessa e lanciarla.

TL; DR: Causa dovrebbe essere immutabile, ecco perché questo controllo viene eseguito in initCause . L'hacking della causa non è il modo migliore per gestire le eccezioni soppresse e di solito causerà molti problemi. Era un grosso difetto di Java più vecchio della versione 7, come la mancanza di eccezioni concatenate prima di Java 1.4.

    
risposta data 29.08.2014 - 15:12
fonte