requirenonnull objects not java java-8 nullpointerexception

not - java objects requirenonnull



¿Por qué debería uno usar Objects.requireNonNull()? (6)

He notado que muchos métodos Java 8 en Oracle JDK usan Objects.requireNonNull() , que internamente arroja NullPointerException si el objeto (argumento) dado es null .

public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }

Pero NullPointerException lanzará de todos modos si se desreferencia un objeto null . Entonces, ¿por qué debería uno hacer esta comprobación nula adicional y lanzar NullPointerException ?

Una respuesta obvia (o beneficio) es que hace que el código sea más legible y estoy de acuerdo. Estoy interesado en conocer otras razones para usar Objects.requireNonNull() al comienzo del método.


Pero NullPointerException se lanzará de todos modos si se desreferencia un objeto nulo. Entonces, ¿por qué debería uno hacer esta comprobación nula adicional y lanzar NullPointerException?

Significa que detecta el problema de forma inmediata y confiable .

Considerar:

  • La referencia no se puede usar hasta más adelante en el método, después de que su código ya haya realizado algunos efectos secundarios
  • La referencia no puede ser desreferenciada en este método
    • Se podría pasar a un código completamente diferente (es decir, causa y error están muy separados en el espacio de código)
    • Podría usarse mucho más tarde (es decir, causa y error están muy separados en el tiempo)
  • Se puede usar en algún lugar donde una referencia nula es válida, pero tiene un efecto no deseado

.NET mejora esto separando NullReferenceException ("desreferenciado un valor nulo") de ArgumentNullException ("no debería haber pasado nulo como argumento, y fue para este parámetro). Desearía que Java hiciera lo mismo, pero incluso con solo una NullPointerException , es mucho más fácil corregir el código si el error se produce en el punto más temprano en el que se puede detectar.


Fallar rapido

El código debería bloquearse lo antes posible. No debe hacer la mitad del trabajo y luego desreferenciar el valor nulo y bloquearse solo luego de dejar la mitad del trabajo realizado y hacer que el sistema esté en un estado no válido.

Esto se conoce comúnmente como "falla temprana" o en.wikipedia.org/wiki/Fail-fast .


Como nota al margen, esto falla rápidamente antes de que Object#requireNotNull se implementara de manera ligeramente diferente antes de java-9 dentro de algunas de las clases jre. Supongamos el caso:

Consumer<String> consumer = System.out::println;

En java-8 esto se compila como (solo las partes relevantes)

getstatic Field java/lang/System.out invokevirtual java/lang/Object.getClass

Básicamente una operación como: yourReference.getClass , que fallaría si yourRefercence es null .

Las cosas han cambiado en jdk-9, donde se compila el mismo código que

getstatic Field java/lang/System.out invokestatic java/util/Objects.requireNonNull

O básicamente Objects.requireNotNull (yourReference)


El uso básico es verificar y lanzar NullPointerException inmediatamente.

Una mejor alternativa (acceso directo) para satisfacer el mismo requisito es la anotación @NotNull .


El uso de requireNonNull() como primeras declaraciones en un método permite identificar en este momento / rápidamente la causa de la excepción.
El seguimiento de la pila indica claramente que la excepción se produjo tan pronto como se introdujo el método porque la persona que llamó no respetó los requisitos / contrato. Pasar un objeto null a otro método puede provocar una excepción a la vez, pero la causa del problema puede ser más complicada de entender ya que la excepción se arrojará en una invocación específica sobre el objeto null que puede estar mucho más lejos.

Aquí hay un ejemplo concreto y real que muestra por qué tenemos que favorecer la falla rápida en general y más particularmente usando Object.requireNonNull() o cualquier forma de realizar una verificación no nula en parámetros diseñados para no ser null .

Suponga que una clase Dictionary que compone un Servicio de LookupService y una List de String representan palabras contenidas en. Estos campos están diseñados para no ser null y uno de ellos se pasa en el constructor del Dictionary .

Ahora suponga una implementación "incorrecta" de Dictionary sin verificación null en la entrada del método (aquí es el constructor):

public class Dictionary { private final List<String> words; private final LookupService lookupService; public Dictionary(List<String> words) { this.words = this.words; this.lookupService = new LookupService(words); } public boolean isFirstElement(String userData) { return lookupService.isFirstElement(userData); } } public class LookupService { List<String> words; public LookupService(List<String> words) { this.words = words; } public boolean isFirstElement(String userData) { return words.get(0).contains(userData); } }

Ahora, invoquemos el constructor del Dictionary con una referencia null para el parámetro de words :

Dictionary dictionary = new Dictionary(null); // exception thrown lately : only in the next statement boolean isFirstElement = dictionary.isFirstElement("anyThing");

La JVM arroja el NPE a esta declaración:

return words.get(0).contains(userData);

Exception in thread "main" java.lang.NullPointerException at LookupService.isFirstElement(LookupService.java:5) at Dictionary.isFirstElement(Dictionary.java:15) at Dictionary.main(Dictionary.java:22)

La excepción se desencadena en la clase LookupService mientras que su origen es bastante anterior (el constructor del Dictionary ). Hace que el análisis general del problema sea mucho menos obvio.
¿Las words null ? ¿ words.get(0) null ? Ambos ? ¿Por qué el uno, el otro o quizás ambos son null ? ¿Es un error de codificación en el Dictionary (constructor? Método invocado?)? ¿Es un error de codificación en LookupService ? (¿constructor? ¿método invocado?)?
Finalmente, tendremos que inspeccionar más código para encontrar el origen del error y, en una clase más compleja, incluso usar un depurador para comprender más fácilmente lo que sucedió.
Pero, ¿por qué una cosa simple (la falta de verificación nula) se convierte en un problema complejo?
Debido a que permitimos el error / falta inicial identificable en una fuga de componente específico en componentes inferiores.
¿Imagine que LookupService no era un servicio local sino un servicio remoto o una biblioteca de terceros con poca información de depuración o imagine que no tenía 2 capas sino 4 o 5 capas de invocaciones de objetos antes de que se detectara el null ? El problema sería aún más complejo de analizar.

Entonces la forma de favorecer es:

public Dictionary(List<String> words) { this.words = Objects.requireNonNull(words); this.lookupService = new LookupService(words); }

De esta manera, no hay dolor de cabeza: obtenemos la excepción tan pronto como se recibe:

// exception thrown early : in the constructor Dictionary dictionary = new Dictionary(null); // we never arrive here boolean isFirstElement = dictionary.isFirstElement("anyThing");

Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at com.Dictionary.(Dictionary.java:15) at com.Dictionary.main(Dictionary.java:24)

Tenga en cuenta que aquí ilustré el problema con un constructor, pero una invocación de método podría tener la misma restricción de verificación no nula.


Porque puedes hacer las cosas explícitas al hacerlo. Me gusta:

public class Foo { private final Bar bar; public Foo(Bar bar) { Objects.requireNonNull(bar, "bar must not be null"); this.bar = bar; }

O más corto:

this.bar = Objects.requireNonNull(bar, "bar must not be null");

Ahora ya sabes :

  • cuando un objeto Foo se creó con éxito usando new()
  • entonces su campo de barra está garantizado como no nulo.

Compare eso con: usted crea un objeto Foo hoy, y mañana invoca un método que usa ese campo y lanza. ¡Lo más probable es que mañana no sepas por qué esa referencia fue nula ayer cuando se pasó al constructor!

En otras palabras: al usar explícitamente este método para verificar las referencias entrantes , puede controlar el punto en el tiempo en que se lanzará la excepción. ¡Y la mayoría de las veces, quieres fallar lo más rápido posible !

Las principales ventajas son:

  • como se dijo, comportamiento controlado
  • depuración más fácil, porque vomita en el contexto de la creación del objeto. ¡En un momento en el que tienes una cierta posibilidad de que tus registros / huellas te digan qué salió mal!
  • y como se muestra arriba: el verdadero poder de esta idea se desarrolla junto con los campos finales . ¡Porque ahora cualquier otro código en su clase puede asumir con seguridad que la bar no es nula, y por lo tanto no necesita ninguna comprobación if (bar == null) en otros lugares!