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

214

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

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

Pero NullPointerExceptionserá arrojado de todos modos si nullse desreferencia un objeto. 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.

usuario4686046
fuente
1
Ese enfoque para la verificación de argumentos te permite hacer trampas cuando escribes pruebas unitarias. Spring también tiene utilidades como esa (ver docs.spring.io/spring/docs/current/javadoc-api/org/… ). Si tiene un "si" y desea tener una cobertura de prueba de unidad alta, debe cubrir ambas ramas: cuando se cumple la condición y cuando no se cumple. Si lo usa Objects.requireNonNull, su código no tiene ramificación, por lo que una sola pasada de una prueba unitaria obtendrá una cobertura del 100% :-)
xorcus

Respuestas:

214

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 fue creado exitosamente usandonew()
  • 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 barno es nulo, ¡y por lo tanto no necesita ningún if (bar == null)control en otros lugares!
GhostCat
fuente
23
Puede hacer que su código sea más compacto escribiendothis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman
3
@KirillRakhman Enseñando a GhostCat algo genial que no sabía -> ganar un boleto en la lotería de votación de GhostCat.
GhostCat
1
Creo que el comentario # 1 aquí es el beneficio real de Objects.requireNonNull(bar, "bar must not be null");. Gracias por este
Kawu
1
¿Cuál fue primero y por qué la gente de "el lenguaje" debería seguir a los autores de alguna biblioteca? ¿Por qué la guayaba no sigue el comportamiento de Java lang?
GhostCat
1
El this.bar = Objects.requireNonNull(bar, "bar must not be null");es aceptable en los constructores, pero potencialmente peligroso en otros métodos, si dos o más variables se establecen en el mismo método. Ejemplo: talkwards.com/2018/11/03/…
Erk
100

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 dejando la mitad del trabajo realizado, haciendo que el sistema esté en un estado no válido.

Esto se conoce comúnmente como "falla temprana" o "falla rápida" .

luk2302
fuente
40

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 por este parámetro). Desearía que Java hiciera lo mismo, pero incluso con solo NullPointerExceptionque, aún es mucho más fácil corregir el código si el error se produce lo antes posible en el que se puede detectar.

Jon Skeet
fuente
13

El uso de las requireNonNull()primeras declaraciones en un método permite identificar ahora / 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 nullobjeto 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 lanzará en una invocación específica sobre el nullobjeto que puede estar mucho más lejos.


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

Supongamos que una Dictionaryclase que compone una LookupServicey una Listde Stringrepresentación de palabras contenidas en. Estos campos están diseñados para ser no nully uno de ellos se pasa en el Dictionary constructor.

Ahora suponga una implementación "incorrecta" Dictionarysin nullverificación en la entrada del método (aquí está 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 al Dictionaryconstructor con una nullreferencia para el wordsparámetro:

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); 
Excepción en el hilo "main" java.lang.NullPointerException
    en LookupService.isFirstElement (LookupService.java:5)
    en Dictionary.isFirstElement (Dictionary.java:15)
    en Dictionary.main (Dictionary.java:22)

La excepción se desencadena en la LookupServiceclase mientras que su origen es bastante anterior (el Dictionaryconstructor). Hace que el análisis general del problema sea mucho menos obvio.
Es words null? Es words.get(0) null? Ambos ? ¿Por qué el uno, el otro o tal vez ambos son null? ¿Es un error de codificación en Dictionary(constructor? Método invocado?)? ¿Es un error de codificación 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, tal vez 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 LookupServiceno se trata de un servicio local, sino de un servicio remoto o de 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 nullse detectara. 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: recibimos 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");
Excepción en el hilo "main" java.lang.NullPointerException
    en java.util.Objects.requireNonNull (Objects.java:203)
    en com.Dictionary. (Dictionary.java:15)
    en 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 comprobación no nula.

davidxxx
fuente
¡Guauu! Gran explicación
Jonathas Nascimento
2

Como nota al margen, este fallo rápido antes Object#requireNotNullse implementó 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)

Eugene
fuente
1

El uso básico es comprobar y lanzar de NullPointerExceptioninmediato.

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

Supun Wijerathne
fuente
1
La anotación no es un reemplazo para el método Objetos, funcionan juntos. No puede decir @ NonNull x = mightBeNull, diría @ NonNull x = Objects.requireNonNull (mightBeNull, "inconceivable!");
Bill K
@BillK lo siento, no te entiendo
Supun Wijerathne
Solo decía que la anotación Nonnull funciona con requireNonNull, no es una alternativa, pero funcionan juntas bastante bien.
Bill K
implementar la naturaleza de falla rápida, es una alternativa ¿verdad? Yh, estoy de acuerdo, no es una alternativa para la tarea.
Supun Wijerathne
Supongo que llamaría a requireNonNull () una "conversión" de @ Nullable a @ NonNull. Si no usa las anotaciones, el método no es realmente muy interesante (ya que todo lo que hace es lanzar un NPE como lo haría el código que protege), aunque muestra claramente su intención.
Bill K
1

Se genera una excepción de puntero nulo cuando accede a un miembro de un objeto que se encuentra nullen un punto posterior. Objects.requireNonNull()comprueba inmediatamente el valor y lanza una excepción al instante sin avanzar.

Mirwise Khan
fuente
0

Creo que debería usarse en constructores de copias y algunos otros casos como DI cuyo parámetro de entrada es un objeto, debe verificar si el parámetro es nulo. En tales circunstancias, puede usar este método estático convenientemente.

Yu Chai
fuente
0

En el contexto de las extensiones del compilador que implementan la comprobación de Nullability (por ejemplo: uber / NullAway ), Objects.requireNonNulldebe usarse con moderación para ocasiones en las que tiene un campo anulable que sabe que no es nulo en un determinado punto de su código.

De esta manera, hay dos usos principales:

  • Validación

    • ya cubierto por otras respuestas aquí
    • Verificación de tiempo de ejecución con NPE general y potencial
  • Marcado de nulabilidad (cambio de @Nullable a @Nonnull)

    • uso mínimo de la verificación en tiempo de ejecución a favor de la verificación en tiempo de compilación
    • solo funciona cuando las anotaciones son correctas (aplicadas por el compilador)

Ejemplo de uso de marcado de nulabilidad:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));
sobreacelerado
fuente
0

Además de todas las respuestas correctas:

Lo usamos en flujos reactivos. Por lo general, los NullpointerExceptions resultantes se envuelven en otras Excepciones dependiendo de su ocurrencia en la secuencia. Por lo tanto, luego podemos decidir fácilmente cómo manejar el error.

Solo un ejemplo: imagina que tienes

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

donde parseAndValidateenvuelve el NullPointerExceptionde requireNonNulla ParsingException.

Ahora puede decidir, por ejemplo, cuándo reintentar o no:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

Sin la comprobación, la excepción NullPointerException se producirá en el savemétodo, lo que dará como resultado un sinfín de reintentos. Incluso vale la pena, imagine una suscripción de larga duración, agregando

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

Ahora RetryExhaustExceptionmatará tu suscripción.

PeMa
fuente