Pregunta ¿Es una mala idea si equals (null) arroja NullPointerException en su lugar?


El contrato de equals con respecto a null, es como sigue:

Para cualquier valor de referencia no nulo x, x.equals(null) debería return false.

Esto es bastante peculiar, porque si o1 != null y o2 == null, entonces nosotros tenemos:

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

El hecho de que o2.equals(o1) throws NullPointerException es algo bueno, ya que nos alerta sobre el error del programador. Y sin embargo, ese error no sería atrapado si por varias razones simplemente lo cambiamos a o1.equals(o2), que simplemente "fallaría silenciosamente" en su lugar.

Entonces las preguntas son:

  • ¿Por qué es una buena idea que o1.equals(o2) debería return false en lugar de arrojar NullPointerException?
  • ¿Sería una mala idea si, de ser posible, reescribimos el contrato para que anyObject.equals(null) siempre tira NullPointerException ¿en lugar?

En comparación con Comparable

Por el contrario, esto es lo que Comparable contrato dice:

Tenga en cuenta que null no es una instancia de ninguna clase, y e.compareTo(null) debería lanzar un NullPointerException aunque e.equals(null) devoluciones false.

Si NullPointerException es apropiado para compareTo¿Por qué no es para equals?

Preguntas relacionadas


Un argumento puramente semántico

Estas son las palabras reales en el Object.equals(Object obj) documentación:

Indica si algunos otro objeto es "igual a" este.

¿Y qué es un objeto?

JLS 4.3.1 Objetos

Un objeto es un instancia de clase o una matriz.

Los valores de referencia (a menudo solo referencias) son punteros a estos objetos, y un especial null referencia, que se refiere a ningún objeto.

Mi argumento desde este ángulo es realmente simple.

  • equals prueba si algunos otro objeto es igual a" this
  • null la referencia no da otro objeto para la prueba
  • Por lo tanto, equals(null) debería tirar NullPointerException

74
2018-05-22 10:45


origen


Respuestas:


A la pregunta de si esta asimetría es inconsistente, creo que no, y los remito a este antiguo Zen Koan:

  • Pregúntale a cualquier hombre si es tan bueno como el próximo hombre y cada uno dirá que sí.
  • Pregúntele a cualquier hombre si es tan bueno como nadie y cada uno dirá que no.
  • No preguntes a nadie si es tan bueno como cualquier hombre y nunca recibirás una respuesta.

En ese momento, el compilador alcanzó la iluminación.


98
2018-05-22 11:29



Una excepción realmente debería ser una excepcional situación. Un puntero nulo podría no ser un error del programador.

Usted citó el contrato existente. Si decides ir en contra de la convención, después de todo este tiempo, cuando todos los desarrolladores de Java esperen que los iguales devuelvan falso, estarás haciendo algo inesperado y desagradable que convertirá a tu clase en paria.

No puedo estar más en desacuerdo. No volvería a escribir iguales para lanzar una excepción todo el tiempo. Reemplazaría cualquier clase que hiciera eso si fuera su cliente.


19
2018-05-22 10:49



Piense en cómo .equals está relacionado con == y .compareTo está relacionado con los operadores de comparación>, <,> =, <=.

Si va a argumentar que el uso de .equals para comparar un objeto con un valor nulo debería arrojar un NPE, entonces tendría que decir que este código debería arrojar uno también:

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

La diferencia entre o1.equals (o2) y o2.equals (o1) es que en el primer caso está comparando algo con null, similar a o1 == o2, mientras que en el segundo caso, el método igual nunca se ejecuta realmente así que no hay comparación en absoluto.

En cuanto al contrato .compareTo, comparar un objeto no nulo con un objeto nulo es como intentar hacer esto:

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

Obviamente esto no compilará. Puedes usar el auto-unboxing para hacer que se compile, pero obtienes un NPE cuando haces la comparación, que es consistente con el contrato .compareTo:

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

8
2018-05-23 10:18



No es que esto sea necesariamente una respuesta a su pregunta, es solo un ejemplo de cuando me parece útil que el comportamiento sea como es ahora.

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

Tal como está, puedo hacerlo.

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

Y no tengo ninguna posibilidad de obtener una NullPointerException. Si se cambió como sugirió, volvería a tener que hacer lo siguiente:

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

¿Es esta una buena razón para que el comportamiento sea como es? No lo sé, pero es un efecto secundario útil.


4
2018-05-22 10:55



Si se tienen en cuenta los conceptos orientados a objetos, y se consideran los roles de todo emisor y receptor, diría que el comportamiento es conveniente. Vea en el primer caso que está preguntando a un objeto si no es igual a nadie. Él DEBE decir "NO, yo no".

En el segundo caso, sin embargo, no tienes una referencia con nadie. Por lo tanto, realmente no estás preguntando a nadie. ESTO debería arrojar una excepción, el primer caso no debería.

Creo que es solo asimétrico si te olvidas de la orientación a objetos y tratas la expresión como una igualdad matemática. Sin embargo, en este paradigma ambos extremos desempeñan diferentes roles, por lo que es de esperar que el orden sea importante.

Como un punto final. Se debe generar una excepción de puntero nulo cuando hay un error en su código. Sin embargo, preguntarle a un objeto si no es nadie, no se debe considerar una falla de programación. Creo que está bien preguntarle a un objeto si no es nulo. ¿Qué pasa si no controlas la fuente que te proporciona el objeto? y esta fuente te envía nulo. ¿Verificaría si el objeto es nulo y solo después verá si son iguales? ¿No sería más intuitivo simplemente comparar los dos y cualquiera que sea el segundo objeto es que la comparación se llevará a cabo sin excepciones?

Honestamente, me enojaría si un método igual dentro de su cuerpo devuelve una excepción de puntero nulo a propósito. Equals está destinado a ser utilizado contra cualquier tipo de objeto, por lo que no debe ser tan exigente con lo que recibe. Si un método equals devolviera npe, lo último en mi mente sería que lo hizo a propósito. Especialmente considerando que es una excepción no verificada. SI levantaras una npe, un chico tendría que recordar comprobar siempre nulo antes de llamar a tu método, o peor aún, rodear la llamada a iguales en un bloque try / catch (Dios odio probar / atrapar bloques) Pero bueno. ..


4
2017-10-14 05:07



Personalmente, prefiero que funcione como lo hace.

los NullPointerException identifica que el problema está en el objeto contra el cual se realiza la operación de igualdad.

Si el NullPointerException fue utilizado como sugieres e intentaste la operación (algo sin sentido) de ...

o1.equals(o1) donde o1 = null ... Es el NullPointerException arrojado porque su función de comparación está jodida o porque o1 es nulo pero no se dio cuenta? Un ejemplo extremo, lo sé, pero con el comportamiento actual, siento que puedes decir fácilmente dónde está el problema.


2
2018-05-22 10:54



En el primer caso o1.equals(o2) devuelve falso porque o1 no es igual a o2, lo cual está perfectamente bien. En el segundo caso, arroja NullPointerException porque o2 es null. Uno no puede llamar a ningún método en un null. Puede ser una limitación de los lenguajes de programación en general, pero tenemos que vivir con eso.

Tampoco es una buena idea lanzar NullPointerException estás violando el contrato para el equals método y hacer las cosas más complejas de lo que debe ser.


2
2018-05-22 11:14



Hay muchas situaciones comunes donde null no es de ninguna manera excepcional, p. puede simplemente representar el caso (no excepcional) en el que una clave no tiene valor, o de lo contrario representa "nada". Por lo tanto, haciendo x.equals(y) con un desconocido y también es bastante común, y siempre hay que verificar null primero sería solo un esfuerzo desperdiciado.

En cuanto a por qué null.equals(y) es diferente, es es un error de programación para llamar alguna método de instancia en una referencia nula en Java, y por lo tanto digno de una excepción. El orden de x y y en x.equals(y) debe ser elegido de tal manera que x se sabe que no null. Yo diría que, en casi todos los casos, este reordenamiento se puede hacer de acuerdo con lo que se conoce sobre los objetos de antemano (por ejemplo, desde su origen, o al verificar en contra de null para otras llamadas a métodos).

Mientras tanto, si ambos objetos son de "nulidad" desconocida, entonces es casi seguro que otro código requiera verificar al menos uno de ellos, o no se puede hacer mucho con el objeto sin arriesgar el NullPointerException.

Y dado que esta es la forma en que se especifica, es un error de programación romper el contrato y generar una excepción para un null argumento para equals. Y si considera la alternativa de requerir que se emita una excepción, entonces cada implementación de equals tendría que hacer un caso especial, y cada llamada a equals con cualquier potencial null el objeto debería verificar antes de llamar.

Eso podría se han especificado de forma diferente (es decir, la condición previa de equals requeriría que el argumento sea nonull), por lo que esto no quiere decir que su argumentación no es válida, pero la especificación actual permite un lenguaje de programación más simple y práctico.


2
2018-05-22 11:15



Tenga en cuenta que el contrato es "para cualquier referencia x no nula". Entonces la implementación se verá así:

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

x Necesita no ser null ser considerado igual null porque la siguiente definición de equals es posible:

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;
}

1
2018-05-22 11:01