sirve que para ejemplo java optimization coding-style nullpointerexception

que - Java: cómo buscar punteros nulos de manera eficiente



jlabel netbeans (14)

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

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

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

En segundo lugar, puede usar un marco 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, lo que puede hacer que el código se ejecute un poco más lento, dependiendo del compilador.

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 fichas que las versiones anteriores.

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

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

¿Qué cheque prefieres y por qué?


¿No estás optimizando un biiiiiiiiiiiii demasiado prematuramente?

Yo solo usaría el primero. Es claro y conciso.

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

El tercero me da escalofríos, y creo que inmediatamente recurriré a la violencia si alguna vez lo veo en código. No está claro qué está haciendo.


Creo que el cuarto y más útil patrón es no hacer nada. Su código arrojará 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 un control nulo solo si tiene algo que ver con eso. Verificar la excepción de lanzamiento es irrelevante en la mayoría de los casos. Simplemente no olvide mencionar en javadoc si el parámetro puede ser nulo.


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.


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 del número de línea, los números de línea cambian. En un sistema de producción real, saber que había un problema en SomeClass en la línea 100 no le proporciona toda la información que necesita. También necesita conocer la revisión del archivo en cuestión y poder acceder a esa revisión. En general, mucha molestia por 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 solo, ya que no puede conocer los posibles efectos secundarios del código C que recibe el llamado.

Mi preferencia es usar # 1 cada vez que sienta que hay una necesidad de un cheque nulo. Tener el nombre de la variable en el mensaje de error es excelente para averiguar rápidamente qué es lo que salió mal.

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


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 = puede incluirse en una expresión en la instrucción if y, por lo tanto, generar errores, a menudo se recomienda cambiar lugares entre la variable y la constante de esta manera:

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

en lugar de:

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

prevenir errores del tipo:

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

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


No deberías estar lanzando NullPointerException. Si desea una NullPointerException, simplemente no verifique el valor y se lanzará automáticamente cuando el parámetro sea nulo e intente desreferenciarlo.

Echa un vistazo a las clases valdate y StringUtils de apache commons lang.
Validate.notNull(variable) lanzará una IllegalArgumentException si "variable" es nula.
Validate.notEmpty(variable) lanzará una IllegalArgumentException si "variable" está vacía (nula o de longitud cero).
Quizás aún mejor:
String trimmedValue = StringUtils.trimToEmpty(variable) garantizará que "trimmedValue" nunca sea nulo. Si "variable" es nulo, "trimmedValue" será la cadena vacía ("").


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

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

Justificación: (1) puedo recorrer un montón de argumentos, (2) el método nullCheck está escondido en una superclase y (3) al final del ciclo,

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

En el método de la superclase, su # 3 se ve bien, pero no arrojaría una excepción (¿cuál es el punto, alguien tiene que manejarlo, y como contenedor de servlets, tomcat lo ignorará, por lo que podría ser así ( )) Saludos, - MS


Prefiero los métodos 4, 5 o 6, donde el n.º 4 se aplica a los métodos API públicos y 5/6 a los métodos internos, aunque el n.º 6 se aplicará con mayor frecuencia a los 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 nulos es no usar valores nulos. Envuelva métodos de biblioteca o de terceros que pueden devolver nulos con guardias nulas, reemplazando el valor con algo que tenga sentido (como una cadena vacía) pero no haga nada cuando se use. Lanza NPE si no se debe pasar un nulo, especialmente en métodos setter donde el objeto pasado no se llama de inmediato.


Primer método. Nunca haría el segundo o el tercer método, a menos que la JVM subyacente los implemente de manera eficiente. De lo contrario, esos dos son solo ejemplos principales de optimización prematura (y el tercero tiene una posible penalización del rendimiento; no es necesario que trates y accedas a metadatos de clase en puntos de acceso generales).

El problema con los NPE es que son aspectos que atraviesan 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 (no dice que el lenguaje es malo, pero es un defecto fundamental ... de cualquier lenguaje que permita punteros o referencias nulas).

Como tal, lo mejor es tratarlo de manera explícita como en el primer método. Todos los otros 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. Ocúpese de él de manera explícita tal como es, en el caso general, menos doloroso en el futuro.



Si bien estoy de acuerdo con el consenso general de preferir evitar el truco getClass (), vale la pena señalar que, a partir de OpenJDK versión 1.8.0_121, javac utilizará el truco 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 . El resultado 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 }

Las instrucciones establecidas en "líneas" 3, 4 y 7 básicamente invocan o.getClass () y descartan el resultado. Si ejecuta NullCheck, obtendrá una NullPointerException lanzada desde la línea 4.

Si esto es algo que la gente de Java concluyó que era una optimización necesaria, o simplemente es un truco barato, no lo sé. Sin embargo, según 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 que el truco getClass (), que produce una verificación nula implícita, pueda ser ligeramente más eficaz que su homólogo explícito. Dicho esto, evitaría usarlo a menos que una comparación cuidadosa mostrara que hacía una diferencia apreciable.

(Curiosamente, el compilador de Eclipse para Java (ECJ) no incluye esta comprobación nula, y ejecutar NullCheck compilado por ECJ no arrojará un NPE.)


Usaría el mecanismo incorporado de afirmación de Java.

assert arg != null;

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


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

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 verificar un registro versus un valor inmediato es rápido, la rama también se va a predecir adecuadamente)

En pocas palabras: a menos que haga algo verdaderamente extraño, sería optimizado por la JVM.


Enfoque n. ° 3: arg.getClass(); es inteligente, pero a menos que este modismo tenga 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 autodocumentados: hay un mensaje de registro que puede usar para aclarar lo que sucedió: este mensaje de registro se utiliza al leer el código y también en tiempo de ejecución. arg.getClass() , tal como está, no es autodocumentado. Puede usar un comentario al menos o aclarar a los revisores del código:

arg.getClass(); // null check

Pero aún no tienes la oportunidad de poner un mensaje específico en el tiempo de ejecución como lo haces con los otros métodos.

Enfoque n. ° 1 vs n. ° 2 (null-check + NPE / IAE vs. assert): Intento seguir pautas como esta:

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

  • Use assert para verificar parámetros en métodos privados
    assert param > 0;

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

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

SIN EMBARGO , dado que esta es una pregunta sobre la captura de posibles problemas null más eficiente, entonces tengo que mencionar que mi método preferido para tratar con null es usar análisis estático, por ejemplo, escribir anotaciones (por ejemplo, @NonNull ) a la JSR-305 . Mi herramienta favorita para verificarlos es:

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

Si es mi proyecto (p. Ej., No una biblioteca con una API pública) y si puedo usar el marco de Checker en todo:

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

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

  • el análisis estático indicará posibles fallas lógicas por adelantado (por ejemplo, que intenté pasar una variable que puede ser nula a un método que solo acepta un parámetro no nulo) en lugar de depender del problema que ocurra en el 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 anotados por tipo y proyectos sin anotaciones de tipo (no es que tenga ninguno de estos )

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

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

5.1.7 Uso de Assertions, IllegalArgumentException y NPE

El lenguaje Java desde hace un par de años ahora hace que una palabra clave assert esté disponible; esta palabra clave se puede usar para realizar solo comprobaciones de depuración. Si bien hay varios usos de este servicio, uno común es verificar los parámetros del método en métodos privados (no públicos). Otros usos son post-condiciones e invariantes.

Referencia: Programación con afirmaciones

Las precondiciones (como las verificaciones de argumentos en métodos privados) suelen ser objetivos fáciles para las aserciones. Las condiciones posteriores e invariantes son en ocasiones menos directas pero más valiosas, ya que las condiciones no triviales tienen más riesgos que romperse.

  • 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 verifica el resultado con el punto original (condición posterior).
  • Ejemplo 2: en las implementaciones DirectPosition.equals (Object), si el resultado es verdadero, la aserción garantiza que hashCode () sea idéntico al requerido por el contrato Object.

Use Assert para verificar los parámetros en los 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 comando:

java -ea MyApp

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

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 IllegalArgumentExceptions para verificar los parámetros en los métodos públicos

El uso de afirmaciones sobre métodos públicos está estrictamente desaconsejado; porque el error que se informa ha sido hecho en el código del cliente - sea honesto y dígales por adelantado con una excepción IllegalArgumentException cuando se hayan equivocado.

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

Use NullPointerException cuando sea necesario

Si es posible, realice sus propias comprobaciones nulas; lanzando una IllegalArgumentException o NullPointerException con información detallada sobre lo que salió 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; }