Java: Cómo comprobar si hay punteros nulos de manera eficiente


Hay algunos patrones para comprobar si un parámetro de un método ha recibido un valor null.

Primero, el clásico. Es común en el código hecho a sí mismo y obvio de entender.

public void method1(String arg) {
  if (arg == null) {
    throw new NullPointerException("arg");
  }
}

En segundo lugar, puede utilizar un framework existente. Ese código se ve un poco mejor porque solo ocupa una sola línea. La desventaja es que potencialmente llama a otro método, que podría hacer que el código se ejecute un poco más lento, dependiendo de la compilación.

public void method2(String arg) {
  Assert.notNull(arg, "arg");
}

En tercer lugar, puede intentar llamar a un método sin efectos secundarios en el objeto. Esto puede parecer extraño al principio, pero tiene menos tokens que las versiones anteriores.

public void method3(String arg) {
  arg.getClass();
}

No he visto el tercer patrón en uso amplio, y se siente casi como si lo hubiera inventado yo mismo. Me gusta por su brevedad, y porque el compilador tiene una buena oportunidad de optimizarlo completamente o convertirlo en una sola instrucción de máquina. También compilo mi código con el número de línea información, por lo que si se lanza un NullPointerException, puedo rastrearlo hasta la variable exacta, ya que solo tengo una verificación por línea.

¿Qué cheque prefieres y por qué?

Author: Roland Illig, 2011-01-25

14 answers

Enfoque #3: arg.getClass(); es inteligente, pero a menos que este modismo vea una adopción generalizada, preferiría los métodos más claros y detallados en lugar de guardar algunos caracteres. Soy un programador de" escribir una vez, leer muchos".

Los otros enfoques son de auto-documentación: hay un mensaje de registro que puede usar para aclarar lo que sucedió - este mensaje de registro se usa cuando se lee el código y también en tiempo de ejecución. arg.getClass(), tal como está, no se documenta a sí mismo. Podrías usar un comentario por lo menos o aclarar a los revisores del código:

arg.getClass(); // null check

Pero todavía no tiene la oportunidad de poner un mensaje específico en el tiempo de ejecución como puede hacerlo con los otros métodos.


Enfoque #1 vs #2 (null-check+NPE / IAE vs assert): Trato de seguir pautas como esta:

Http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • Utilice assert para comprobar los parámetros en privado métodos
    assert param > 0;

  • Use null check + IllegalArgumentException para verificar parámetros en métodos públicos
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • Use null check + NullPointerException donde sea necesario
    if (getChild() == null) throw new NullPointerException("node must have children");


SIN EMBARGO, dado que esta es la pregunta puede ser acerca de la captura de potencial null problemas más eficientemente, entonces tengo que mencionar que mi método preferido para tratar con null es el uso de análisis estático, por ejemplo, tipo anotaciones (por ejemplo, @NonNull) a la JSR-305. Mi herramienta favorita para comprobarlos es:

El Framework Checker:
Tipos conectables personalizados para Java
https://checkerframework.org/manual/#checker-guarantees

Si es mi proyecto (por ejemplo, no es una biblioteca con una API pública) y si puedo usar el Marco Checker en todo:

  • Puedo documentar mi intención más claramente en la API (por ejemplo, este parámetro puede no ser nulo (el por defecto), pero este puede ser null (@Nullable; el método puede devolver null; etc). Esta anotación está justo en la declaración, en lugar de más lejos en el Javadoc, por lo que es mucho más probable que se mantenga.

  • el análisis estático es más eficiente que cualquier comprobación de tiempo de ejecución

  • El análisis estático marcará posibles fallas lógicas de antemano (por ejemplo, que intenté pasar una variable que puede ser nula a un método que solo acepta una no nula parámetro) en lugar de depender del problema que ocurre en tiempo de ejecución.

Otra ventaja es que la herramienta me permite poner las anotaciones en un comentario (por ejemplo, `/@Nullable/), por lo que mi código de biblioteca puede ser compatible con proyectos con anotaciones de tipo y proyectos sin anotaciones de tipo (no es que tenga ninguno de estos).


En caso de que el enlace se muera de nuevo , aquí está la sección de GeoTools Developer Guía:

Http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7 Uso de Aserciones, excepción ilegal y NPE

Desde hace un par de años, el lenguaje Java ha hecho disponible una palabra clave assert; esta palabra clave solo se puede usar para realizar comprobaciones de depuración. Si bien hay varios usos de esta facilidad, uno común es verificar los parámetros del método en métodos privados (no públicos). Otros usos son post-condiciones y invariable.

Referencia: Programación Con Aserciones

Las precondiciones (como las comprobaciones de argumentos en métodos privados) suelen ser objetivos fáciles para las aserciones. Post-condiciones e invariantes son en algún momento menos directa pero más valiosa, ya que las condiciones no triviales tienen más riesgos que romper.

  • Ejemplo 1: Después de una proyección de mapa en el módulo de referencia, una aserción realiza la proyección de mapa inversa y comprueba resultado con el punto original (post-condición).
  • Ejemplo 2: En posición directa.es igual a implementaciones (Objeto), si el resultado es true, entonces la aserción asegura que hashCode() son idénticos a los requeridos por el contrato de objeto.

Use Assert para verificar Parámetros en métodos privados

private double scale( int scaleDenominator ){
 assert scaleDenominator > 0;
 return 1 / (double) scaleDenominator;
}

Puede habilitar aserciones con el siguiente parámetro de línea de comandos:

java -ea MyApp

Solo puede activar las aserciones de GeoTools con el siguiente parámetro de línea de comandos:

java -ea:org.geotools MyApp

Puede deshabilitar las aserciones para un paquete específico como se muestra aquí:

java -ea:org.geotools -da:org.geotools.referencing MyApp

Use Excepciones Ilegalargumentexceptions para verificar Parámetros en Métodos Públicos

El uso de asserts en métodos públicos está estrictamente desaconsejado; porque el error que se informa se ha cometido en el código del cliente-sea honesto y dígales por adelantado con una excepción ilegal cuando la hayan cagado.

public double toScale( int scaleDenominator ){
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}

Uso NullPointerException donde sea necesario

Si es posible, realice sus propias comprobaciones null; lanzando una Ilegalargumentexception o NullPointerException con información detallada sobre lo que ha ido mal.

public double toScale( Integer scaleDenominator ){
 if( scaleDenominator == null ){
 throw new NullPointerException( "scaleDenominator must be provided");
 }
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}
 22
Author: Bert F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-21 05:47:38

¿No estás optimizando un biiiiiiiiiiiiiiiiiiit demasiado prematuramente!?

Solo usaría el primero. Es claro y conciso.

Rara vez trabajo con Java, pero asumo que hay una manera de hacer que Assert solo opere en compilaciones de depuración, por lo que sería un no-no.

La tercera me da escalofríos, y creo que recurriría inmediatamente a la violencia si alguna vez la viera en código. No está claro lo que está haciendo.

 11
Author: Willem van Rumpt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-14 03:42:45

No debe lanzar NullPointerException. Si quieres un NullPointerException, sólo dont comprobar el valor y será expulsada automáticamente cuando el parámetro es null y se intenta eliminar la referencia.

Echa un vistazo a las clases apache commons lang Validate y StringUtils.
Validate.notNull(variable) lanzará una excepción IllegalArgumentException si "variable" es null.
Validate.notEmpty(variable) lanzará una excepción IllegalArgumentException si "variable"está vacía (null o zero length".
Tal vez incluso mejor:
String trimmedValue = StringUtils.trimToEmpty(variable) garantizará que "trimmedValue" nunca sea nulo. Si " variable "es null," trimmedValue "será la cadena vacía ("").

 5
Author: DwB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 16:49:28

Puede usar la clase de utilidad Objects.

public void method1(String arg) {
  Objects.requireNonNull(arg);
}

Véase http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull%28T%29

 5
Author: räph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-30 07:40:54

El primer método es mi preferencia porque transmite la mayor intención. A menudo hay atajos que se pueden tomar en la programación, pero mi opinión es que el código más corto no siempre es mejor código.

 3
Author: Bnjmn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:43:03

En mi opinión, hay tres problemas con el tercer método:

  1. La intención no está clara para el lector casual.
  2. Aunque tenga información sobre el número de línea, los números de línea cambian. En un sistema de producción real, saber que hubo un problema en SomeClass en la línea 100 no le da toda la información que necesita. También necesita conocer la revisión del archivo en cuestión y ser capaz de llegar a esa revisión. Con todo, un montón de problemas para lo que parece ser muy poco beneficio.
  3. No está del todo claro por qué crees que la llamada a arg.getClass puede optimizarse. Es un método nativo. A menos que HotSpot esté codificado para tener un conocimiento específico del método para esta eventualidad exacta, probablemente dejará la llamada en paz ya que no puede saber acerca de los posibles efectos secundarios del código C que se llama.

Mi preferencia es usar #1 cada vez que siento que hay una necesidad de una comprobación nula. Tener el nombre de la variable en el mensaje de error es ideal para averiguar qué es exactamente lo que ha salido mal.

P.d. No creo que optimizar el número de tokens en el archivo fuente sea un criterio muy útil.

 3
Author: NPE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:58:22

X = = null es súper rápido, y puede ser un par de relojes de CPU (incl. la predicción de rama que va a tener éxito). AssertNotNull estará en línea, así que no hay diferencia.

X. getClass() no debería ser más rápido que x==null incluso si usa trap. (razón: la x estará en algún registro y comprobar un registro vs un valor inmediato es rápido, la rama se va a predecir correctamente también)

En pocas palabras: a menos que hagas algo realmente raro, sería optimizado por el JVM.

 2
Author: bestsss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 17:00:24

La primera opción es la más fácil y también la más clara.

No es común en Java, pero en C y C++ donde el operador = se puede incluir en una expresión en la instrucción if y por lo tanto conducir a errores, a menudo se recomienda cambiar los lugares entre la variable y la constante de la siguiente manera:

if (NULL == variable) {
   ...
}

En lugar de:

if (variable == NULL) {
   ...
}

Previniendo errores del tipo:

if (variable = NULL) { // Assignment!
   ...
}

Si realiza el cambio, el compilador encontrará ese tipo de errores para usted.

 1
Author: Marcelo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:49:35

Usaría el mecanismo integrado de Java assert.

assert arg != null;

La ventaja de esto sobre todos los otros métodos es que se puede desactivar.

 0
Author: biziclop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:46:20

Prefiero el método 4, 5 o 6, con #4 siendo aplicado a métodos API públicos y 5 / 6 para métodos internos, aunque #6 sería aplicado con más frecuencia a métodos públicos.

/**
 * Method 4.
 * @param arg A String that should have some method called upon it. Will be ignored if
 * null, empty or whitespace only.
 */
public void method4(String arg) {
   // commons stringutils
   if (StringUtils.isNotBlank(arg) {
       arg.trim();
   }
}

/**
 * Method 5.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // Let NPE sort 'em out.
   arg.trim();
}

/**
 * Method 6.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // use asserts, expect asserts to be enabled during dev-time, so that developers
   // that refuse to read the documentations get slapped on the wrist for still passing
   // null. Assert is a no-op if the -ae param is not passed to the jvm, so 0 overhead.

   assert arg != null : "Arg cannot be null"; // insert insult here.
   arg.trim();
}

La mejor solución para manejar nulls es no usar nulls. Wrap métodos de terceros o de bibliotecas que pueden devolver nulls con null guards, reemplazando el valor con algo que tiene sentido (como una cadena vacía) pero no hace nada cuando se usa. Lanzar NPE si un null realmente no debe ser pasado, especialmente en métodos setter donde el objeto pasado no se llama de inmediato.

 0
Author: fwielstra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:50:27

No hay voto para este, pero uso una ligera variación de #2, como

erStr += nullCheck (varName, String errMsg); // returns formatted error message

Rationale: (1) I can loop over a bunch of arguments, (2) The nullCheck method is tucked away in a superclass and (3) at the end of the loop,

if (erStr.length() > 0)
    // Send out complete error message to client
else
    // do stuff with variables

En el método superclass, tu #3 se ve bien, pero no lanzaría una excepción (cuál es el punto, alguien tiene que manejarlo, y como un contenedor de servlet, tomcat lo ignorará, por lo que bien podría ser esto()) Saludos, - M. S.

 0
Author: Manidip Sengupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 17:15:26

Primer método. Nunca haría el segundo o el tercer método, no a menos que sean implementados eficientemente por la JVM subyacente. De lo contrario, esos dos son solo ejemplos principales de optimización prematura (con el tercero teniendo una posible penalización de rendimiento: no desea tratar y acceder a metadatos de clase en puntos de acceso generales.)

El problema con los NPE es que son cosas que cruzan muchos aspectos de la programación (y mis aspectos, me refiero a algo más profundo y más profundo que AOP). Es un problema de diseño del lenguaje (sin decir que el lenguaje es malo, pero que es una deficiencia fundamental... de cualquier lenguaje que permita punteros nulos o referencias.)

Como tal, es mejor tratar simplemente con él explícitamente como en el primer método. Todos los demás métodos son intentos (fallidos) de simplificar un modelo de operaciones, una complejidad inevitable que existe en el modelo de programación subyacente.

Es una bala que no podemos evitar morder. Tratar con él de forma explícita como en el caso general, que es menos doloroso el camino.

 0
Author: luis.espinal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-11 11:24:52

Aunque estoy de acuerdo con el consenso general de preferir evitar el hackeo getClass (), vale la pena señalar que, a partir de la versión 1.8.0_121 de OpenJDK, javac usará el hackeo getClass() para insertar comprobaciones nulas antes de crear expresiones lambda. Por ejemplo, considere:

public class NullCheck {
  public static void main(String[] args) {
    Object o = null;
    Runnable r = o::hashCode;
  }
}

Después de compilar esto con javac, puede usar javap para ver el bytecode ejecutando javap -c NullCheck. La salida es (en parte):

Compiled from "NullCheck.java"
public class NullCheck {
  public NullCheck();
    Code:
       0: aload_0
       1: invokespecial #1    // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aconst_null
       1: astore_1
       2: aload_1
       3: dup
       4: invokevirtual #2    // Method java/lang/Object.getClass:()Ljava/lang/Class;
       7: pop
       8: invokedynamic #3, 0 // InvokeDynamic #0:run:(Ljava/lang/Object;)Ljava/lang/Runnable;
      13: astore_2
      14: return
}

El conjunto de instrucciones en las "líneas" 3, 4 y 7 están básicamente invocando o. getClass(), y descartando el resultado. Si ejecuta NullCheck, obtendrá una excepción NullPointerException lanzada desde la línea 4.

Si esto es algo que la gente de Java concluyó que era una optimización necesaria, o es solo un truco barato, no lo sé. Sin embargo, basado en el comentario de John Rose en https://bugs.openjdk.java.net/browse/JDK-8042127?focusedCommentId=13612451&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451, Sospecho que de hecho puede ser el caso que el hackeo getClass (), que produce una comprobación null implícita, puede ser ligeramente más performante que su contraparte explícita. Dicho esto, evitaría usarlo a menos que el benchmarking cuidadoso mostrara que hacía alguna diferencia apreciable.

(Curiosamente, el Compilador de Eclipse Para Java (ECJ) hace no incluir esta comprobación nula, y ejecutar NullCheck como compilado por ECJ no arrojará un n NPE.)

 0
Author: Ian Robertson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-27 20:28:02

Creo que el cuarto y más útil patrón es no hacer nada. Su código lanzará NullPointerException u otra excepción un par de líneas más tarde (si null es un valor ilegal) y funcionará bien si null está bien en este contexto.

Creo que debe realizar una comprobación nula solo si tiene algo que ver con ella. La comprobación para lanzar la excepción es irrelevante en la mayoría de los casos. Simplemente no olvides mencionar en javadoc si el parámetro puede ser null.

 -1
Author: AlexR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-25 15:45:58