java occurred - È una cattiva idea se uguale a(null)genera invece NullPointerException?





value the (10)


Alla domanda se questa asimmetria sia incoerente, penso di no, e ti rimando a questo antico koan Zen:

  • Chiedi a qualsiasi uomo se è buono come il prossimo e ognuno dirà di sì.
  • Chiedi a qualsiasi uomo se è buono come nessuno e ognuno dirà di no.
  • Chiedi a nessuno se è buono come qualsiasi uomo e non avrai mai una risposta.

In quel momento, il compilatore raggiunse l'illuminazione.

Il contratto di equals a null , è il seguente:

Per qualsiasi valore di riferimento non nullo x , x.equals(null) deve return false .

Questo è piuttosto particolare, perché se o1 != null e o2 == null , allora abbiamo:

o1.equals(o2) // returns false
o2.equals(o1) // throws NullPointerException

Il fatto che o2.equals(o1) throws NullPointerException è una buona cosa, perché ci avvisa dell'errore del programmatore. Eppure, o1.equals(o2) non sarebbe stato catturato se per vari motivi lo avessimo spostato su o1.equals(o2) , che invece sarebbe "silenziosamente fallito".

Quindi le domande sono:

  • Perché è una buona idea che o1.equals(o2) debba return false invece di lanciare NullPointerException ?
  • Sarebbe una cattiva idea, se possibile, riscrivere il contratto in modo che anyObject.equals(null) lanci invece NullPointerException ?

In confronto con Comparable

Al contrario, questo è ciò che dice il contratto Comparable :

Si noti che null non è un'istanza di alcuna classe, e e.compareTo(null) dovrebbe lanciare una NullPointerException anche se e.equals(null) restituisce false .

Se NullPointerException è appropriato per compareTo , perché non lo è per gli equals ?

Domande correlate

Un argomento puramente semantico

Queste sono le parole effettive nella documentazione equals :

Indica se qualche altro oggetto è "uguale a" questo.

E cos'è un oggetto?

Oggetti JLS 4.3.1

Un oggetto è un'istanza di classe o un array.

I valori di riferimento (spesso solo riferimenti ) sono puntatori a questi oggetti e uno speciale riferimento null , che fa riferimento a nessun oggetto .

La mia argomentazione da questo punto di vista è davvero semplice.

  • equals verificare se qualche altro oggetto è "uguale a" this
  • null riferimento null non fornisce altri oggetti per il test
  • Pertanto, equals(null) dovrebbe generare NullPointerException



Nel primo caso o1.equals(o2) restituisce false perché o1 non è uguale a o2 , che è perfettamente a posto. Nel secondo caso, lancia NullPointerException perché o2 è null . Non è possibile chiamare alcun metodo su un valore null . Potrebbe essere una limitazione dei linguaggi di programmazione in generale, ma dobbiamo conviverci.

Inoltre, non è una buona idea lanciare NullPointerException per violare il contratto per il metodo degli equals e rendere le cose più complesse di quanto NullPointerException essere.




Si noti che il contratto è "per qualsiasi riferimento x non nullo". Quindi l'implementazione sarà simile a:

if (x != null) {
    if (x.equals(null)) {
        return false;
    }
}

x non deve essere null per essere considerato uguale a null perché è possibile la seguente definizione di equals :

public boolean equals(Object obj) {
    // ...
    // If someMember is 0 this object is considered as equal to null.
    if (this.someMember == 0 and obj == null) {
         return true;
    }
    return false;
}



Pensa a come .equals è correlato a == e .compareTo è correlato agli operatori di confronto>, <,> =, <=.

Se sosterrai che l'uso di .equals per confrontare un oggetto con null dovrebbe generare un NPE, allora dovresti dire che questo codice dovrebbe lanciarne uno:

Object o1 = new Object();
Object o2 = null;
boolean b = (o1 == o2); // should throw NPE here!

La differenza tra o1.equals (o2) e o2.equals (o1) è che nel primo caso si confronta qualcosa a null, simile a o1 == o2, mentre nel secondo caso, il metodo equals non viene mai effettivamente eseguito quindi non c'è paragone affatto.

Per quanto riguarda il contratto .compare, il confronto di un oggetto non nullo con un oggetto nullo è come provare a fare questo:

int j = 0;
if(j > null) { 
   ... 
}

Ovviamente questo non verrà compilato. È possibile utilizzare l'auto-unboxing per farlo compilare, ma si ottiene un NPE quando si effettua il confronto, che è coerente con il contratto .compare:

Integer i = null;
int j = 0;
if(j > i) { // NPE
   ... 
}



Non che questa sia necessariamente una risposta alla tua domanda, è solo un esempio di quando trovo utile che il comportamento sia come è ora.

private static final String CONSTANT_STRING = "Some value";
String text = getText();  // Whatever getText() might be, possibly returning null.

Così come posso fare.

if (CONSTANT_STRING.equals(text)) {
    // do something.
}

E non ho alcuna possibilità di ottenere una NullPointerException. Se fosse cambiato come avevi suggerito, tornerei a dover fare:

if (text != null && text.equals(CONSTANT_STRING)) {
    // do something.
}

È una buona ragione per il comportamento di essere così com'è ?? Non lo so, ma è un utile effetto collaterale.




Penso che riguardi la convenienza e, cosa più importante, la coerenza: consentire ai null di essere parte del confronto evita di dover eseguire un controllo null e implementare la semantica di ciò ogni volta che viene chiamato equals . null riferimenti null sono legali in molti tipi di raccolta, quindi ha senso che possano apparire come il lato destro del confronto.

L'uso di metodi di istanza per l'uguaglianza, il confronto, ecc. Rende necessariamente la disposizione asimmetrica - un piccolo problema per l'enorme guadagno di polimorfismo. Quando non ho bisogno di polimorfismo, a volte creo un metodo statico simmetrico con due argomenti, MyObject.equals(MyObjecta, MyObject b) . Questo metodo controlla quindi se uno o entrambi gli argomenti sono riferimenti null. Se voglio in modo specifico escludere riferimenti null, creo un metodo aggiuntivo, ad es. equalsStrict() o simile, che esegue un controllo Null prima di delegare all'altro metodo.




Un'eccezione dovrebbe essere davvero una situazione eccezionale . Un puntatore nullo potrebbe non essere un errore del programmatore.

Hai citato il contratto esistente. Se decidi di andare contro le convenzioni, dopo tutto questo tempo, quando ogni sviluppatore Java si aspetta che gli uguali tornino falsi, farai qualcosa di inaspettato e sgradito che renderà la tua classe un paria.

Non potrei essere in disaccordo di più. Non vorrei riscrivere gli uguali per lanciare un'eccezione tutto il tempo. Sostituirei qualsiasi classe che lo facesse se fossi il suo cliente.




Questa è una domanda complicata. Per la retrocompatibilità non è possibile farlo.

Immagina il seguente scenario

void m (Object o) {
 if (one.equals (o)) {}
 else if (two.equals (o)) {}
 else {}
}

Ora con equals return false clausola else verrà eseguita, ma non quando si genera un'eccezione.

Anche null non è proprio uguale a dire "2", quindi ha perfettamente senso restituire false. Allora probabilmente è meglio insistere su null.equals ("b") per restituire anche false :))

Ma questo requisito crea una relazione equa strana e non simmetrica.




Ci sono molte situazioni comuni in cui null non è in alcun modo eccezionale, ad esempio può semplicemente rappresentare il caso (non eccezionale) in cui una chiave non ha valore, o altrimenti significa "nulla". Quindi, fare x.equals(y) con una y sconosciuta è anche abbastanza comune, e dover sempre verificare la presenza di null prima sarebbe solo uno sforzo inutile.

Per quanto riguarda perché null.equals(y) è diverso, è un errore di programmazione chiamare qualsiasi metodo di istanza su un riferimento null in Java , e quindi degno di un'eccezione. L'ordinamento di y in x.equals(y) dovrebbe essere scelto in modo tale che x sia noto per non essere null . Direi che in quasi tutti i casi questo riordino può essere fatto sulla base di ciò che è noto sugli oggetti in anticipo (ad esempio, dalla loro origine, o controllando contro null per altre chiamate di metodo).

Nel frattempo, se entrambi gli oggetti sono di "nullità" sconosciuta, l'altro codice richiede quasi certamente di controllarne almeno uno, oppure non si può fare molto con l'oggetto senza NullPointerException rischio NullPointerException .

E poiché questo è il modo in cui viene specificato, è un errore di programmazione interrompere il contratto e generare un'eccezione per un argomento null a equals . E se si considera l'alternativa di richiedere un'eccezione da lanciare, allora ogni implementazione di equals dovrebbe farne un caso speciale, e ogni chiamata a equals con qualsiasi oggetto potenzialmente null dovrebbe controllare prima di chiamare.

Potrebbe essere stato specificato diversamente (cioè, la precondizione degli equals richiederebbe che l'argomento non sia null ), quindi questo non vuol dire che la tua argomentazione non sia valida, ma le specifiche attuali rendono un linguaggio di programmazione più semplice e pratico.




Quando si desidera testare IDENTITY (stessa posizione in memoria):

ReferenceEquals(a, b)

Gestisce i null. E non è superabile. Sicuro al 100%.

Ma assicurati di volere davvero il test di identità. Considera quanto segue:

ReferenceEquals(new String("abc"), new String("abc"))

che restituisce false . In contrasto:

Object.Equals(new String("abc"), new String("abc"))

e

(new String("abc")) == (new String("abc"))

entrambi restituiscono il true .

Se ti aspetti una risposta true in questa situazione, allora vuoi un test di UGUAGLIANZA, non un test di IDENTITÀ. Vedi la parte successiva.

Quando si desidera testare l'EQUALITÀ (stesso contenuto):

  • Usa " a == b " se il compilatore non si lamenta.

  • Se questo viene rifiutato (se il tipo di variabile a non definisce l'operatore "=="), quindi usa " Object.Equals(a, b) ".

  • SE si è all'interno della logica in cui è noto che non è nullo , POI si può usare il più leggibile " a.Equals(b) ". Ad esempio, "this.Equals (b)" è sicuro. Oppure se "a" è un campo che è inizializzato in fase di costruzione e il costruttore genera un'eccezione se viene passato nulla come valore da utilizzare in quel campo.

ORA, per rispondere alla domanda originale:

D: Sono suscettibili di essere sovrascritte in alcune classi, con codice che non gestisce correttamente null, con conseguente eccezione?

A: Sì. L'unico modo per ottenere il test di EQUALITÀ sicuro al 100% è di testare preventivamente il nulla.

Ma dovresti? Il bug sarebbe in quella (ipotetica futura cattiva classe), e sarebbe un tipo diretto di fallimento. Facile da correggere e correggere (da chiunque fornisca la classe). Dubito che sia un problema che accade spesso, o persiste a lungo quando succede.

Più dettagliato A: Object.Equals(a, b) è più probabile che funzioni di fronte a una classe scritta male. Se "a" è nullo, la classe Object lo gestirà da solo, quindi non vi è alcun rischio. Se "b" è nullo, il tipo DYNAMIC (runtime non compilato) di "a" determina quale metodo "uguale" viene chiamato. Il metodo chiamato deve funzionare correttamente quando "b" è nullo. A meno che il metodo chiamato non sia scritto molto male, il primo passo che fa è determinare se "b" è un tipo che comprende.

Quindi Object.Equals(a, b) è un ragionevole compromesso tra readability / coding_effort e safety.





java null equals nullpointerexception