[java] È una cattiva idea se uguale a (null) genera invece NullPointerException?



Answers

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.

Question

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



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.




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.




Personalmente, preferisco che funzioni come fa.

NullPointerException identifica che il problema si trova nell'oggetto rispetto al quale viene eseguita l'operazione di uguaglianza.

Se la NullPointerException stata utilizzata come suggerisci e hai provato l'operazione (sorta di inutile) di ...

o1.equals(o1) dove o1 = null ... Viene NullPointerException l' NullPointerException perché la funzione di confronto è avvitata o perché o1 è nullo ma non l'hai capito? Un esempio estremo, lo so, ma con il comportamento attuale sento che si può capire facilmente dove si trova il problema.




Related