2011-01-25 8 views
23

Existen algunos patrones para verificar si un parámetro a un método ha recibido un valor de null.Java: cómo buscar punteros nulos de manera eficiente

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 utilizar 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, que podría 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, se puede tratar de llamar a un método sin efectos secundarios sobre 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 uso amplio, y se siente casi 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 arroja un NullPointerException, puedo rastrearlo hasta la variable exacta, ya que solo tengo una de esas comprobaciones por línea.

¿Qué control prefiere y por qué?

Respuesta

21

Enfoque # 3:arg.getClass(); es inteligente, pero a no ser que este lenguaje ver adopción generalizada, prefiero los métodos más claras, más prolija en lugar de ahorrar unos cuantos 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 sucedido: 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. Se puede usar un comentario o al menos aclarar a los colaboradores del código:

arg.getClass(); // null check 

Pero aún así no tener la oportunidad de poner un mensaje específico en el tiempo de ejecución como se hace con los otros métodos.


Enfoque # 1 vs # 2 (null-cheque + NPE/IAE vs afirman): trato de seguir las directrices de la siguiente manera:

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

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

  • Uso cheque nulo + IllegalArgumentException para comprobar los parámetros de métodos públicos
    if (param == null) throw new IllegalArgumentException("param cannot be null");

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


Sin embargo, ya que esta es la pregunta puede ser acerca de la captura potenciales null temas más eficiente, entonces tengo que hablar de mi método preferido para hacer frente a null está utilizando el análisis estático, por ejemplo, escriba anotaciones (por ejemplo, @NonNull) a la JSR-305. Mi herramienta favorita para el control de ellas es:

El Marco Checker:
tipos conectable personalizado para Java
https://checkerframework.org/manual/#checker-guarantees

Si es mi proyecto (por ejemplo, no una biblioteca con una API pública) y si puedo utilizar el Marco corrector largo:

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

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

  • análisis estático marcará potenciales defectos de lógica de antelación (por ejemplo, que trató de pasar una variable que puede ser nulo a un método que sólo acepta un parámetro no nulo) en lugar de depender del problema que ocurre 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 la biblioteca puede anotado tipo no compatible con los proyectos de tipo-anotada y proyectos (no es que tenga ninguno de estos).


En caso de que el enlace va muertos, aquí está la sección de Guía GeoTools Desarrollador:

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

5.1.7 Uso de las afirmaciones, 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 esta instalación, 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:Programming With Assertions

condiciones previas (como cheques de argumentos en los métodos privados) son objetivos típicamente fáciles para afirmaciones. Las condiciones posteriores e invariantes son en algún momento menos directas pero más valiosas, ya que las condiciones no triviales tienen más riesgos de romperse.

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

Uso Assert para comprobar los parámetros en los métodos privados

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

Puede activar afirmaciones con el siguiente parámetro de línea de comando:

java -ea MyApp 

Se puede activar sólo GeoTools afirmaciones con la siguiente Parámetro de línea de comando:

java -ea:org.geotools MyApp 

Puede desactivar las afirmaciones de un paquete específico como se muestra aquí:

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

Use IllegalArgumentExceptions a comprobar los parámetros de Métodos públicos

El uso de métodos afirma el público no se recomienda estrictamente; porque el error que se informa ha sido hecho en el código del cliente - sea honesto y dígales por adelantado con una 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; 
} 

Uso NullPointerException cuando sea necesario

Si es posible llevar a cabo sus propios cheques nulos; 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; 
} 
+1

Soy un fanático del enfoque de verificación estática. ¿Cómo lidiar con el problema de las bibliotecas de terceros que no proporcionan tales anotaciones? ¿Se puede anular todo por defecto? ¿Todo no es nulo por defecto? ¿Puedes hacer modificaciones específicas de este valor predeterminado? –

+1

@Martinho - Dos enfoques: (1) ignórelo porque el predeterminado funciona para la mayoría de los parámetros/métodos, y el uso puede usar 'SuppressWarnings' para manejar el resto, y (2) la herramienta proporciona una manera de anotar una biblioteca de terceros por separado . –

3

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.

+2

no hago mucho Java, por lo Tendré que preguntar: ¿deberías estar lanzando NullPointerExceptions? ¿No es ese tipo de "reservado" para el tiempo de ejecución? ¿No debería ser algo más en la línea de IllegalArgumentException? –

+0

@Martinho Fernandes Está bien lanzar un 'NPE', si coincide con la condición de error, casi nunca deberías atrapar un' NPE'. – biziclop

+0

Es perfectamente apropiado lanzar un NPE en el caso de un nulo que no esperaba. Cosas como OOM deberían reservarse para el tiempo de ejecución. – gubby

11

¿No estás optimizando una biiiiiiiiiiiii demasiado prematuramente?

Solo usaría la primera. Es claro y conciso.

Rara vez trabajo con Java, pero supongo que hay una forma de 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 recurriría inmediatamente a la violencia si alguna vez lo vi en el código. No está claro qué está haciendo.

+5

EXACTAMENTE esto. No hay forma en el mundo en que puedas encontrar un problema de rendimiento con una comprobación nula. Incluso si el compilador JIT no optimiza esto, y probablemente lo hará, el tiempo es insignificante. Compare los segundos micro o nano que esto lleva con los milisegundos de acceso a la red o al disco. Hay peces más grandes para freír. – rfeak

+0

Cuando usted no tiene acceso a la red o de disco (que yo no), y cuando el código se llama mil millones billones de veces, incluso un solo materias instrucciones de la máquina. Por eso estoy preguntando. –

+0

Cuando viste un ciclo 'for' por primera vez, ¿recurriste a la violencia también? Aún así, el uso de esta sintaxis extraña se ha convertido en un conocimiento común, y muchas personas no tienen que pensar en ello cuando lo ven ahora. Personalmente, me acostumbré a la tercera variante, y no tengo que pensar más en eso. Es como ver '(void) argv' en código C, que al principio parece una completa tontería. –

-1

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 una comprobación nula 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.

+3

No estoy de acuerdo. Podría ser "un par de líneas más tarde" o arbitrariamente lejano, ya que está permitiendo que los nulos pasen ilesos a través de cada método. Si rechaza de manera preventiva argumentos nulos, la excepción se arroja lo más cerca posible del culpable. –

+0

@Martinho Fernandes - 1 para fallar rápido: http://stackoverflow.com/questions/2807241/what-does-the-expression-fail-early-mean-and-when-would-you-want-to-do -so/2807375 # 2807375 –

0

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.

+0

¿Cuidar para elaborar? – biziclop

+0

Incluí esto en mi propia respuesta. Elaboración: asegura que otros desarrolladores no pasen nulos, de modo que cualquier persona que de manera incorrecta pase nulos a un método que no acepte valores nulos recibirá una palmada en la muñeca. Sin embargo, recuerde siempre que la palabra clave assert es y debe usarse como una herramienta solo para desarrolladores: las máquinas de producción tendrán habilitadas/no/aseguradas. – fwielstra

+0

@Cthulhu ¿Y cómo es eso diferente de lo que escribí? A diferencia de todas las demás soluciones, 'assert' no se pega a la muñeca, ya que se puede apagar. Incluso mejor, en se puede apagar y encender selectivamente sin recompilar el código. – biziclop

3

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

  1. La intención es claro 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é cree que se puede optimizar la llamada al arg.getClass. 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 siempre que sienta que hay una necesidad de una verificación nula. Tener el nombre de la variable en el mensaje de error es excelente para averiguar rápidamente qué es lo que salió mal.

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

1

La primera opción es la más fácil y también es 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) { 
    ... 
} 

evitando errores del tipo:

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

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

+1

¿Está recomendando condiciones Yoda en Java? Si no, ¿por qué divaga sobre C y C++ en una pregunta de Java? –

+1

No lo considero fuera de tema. Respondí la pregunta y luego traté de contribuir con una pequeña sugerencia de que, después de todo, no está tan lejos de Java. Lo siento si he violado una regla (aunque no creo que lo hice.) – Marcelo

+0

Doy un voto + a esto, ya que soy uno de los desarrolladores de Java que escriben (null == x) y puedo ver la razón por la Marcelo lo mencionó. – yclian

0

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 aplica 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.

+0

+1 para "La mejor solución para manejar valores nulos es no usar valores nulos". –

5

No debería 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 de validación de Apache commons y StringUtils.
Validate.notNull(variable) lanzará una IllegalArgumentException si "variable" es nula.
Validate.notEmpty(variable) arrojará una IllegalArgumentException si "variable" es vacío (nulo o cero longitud"
Tal vez aún mejor:.
String trimmedValue = StringUtils.trimToEmpty(variable) garantizará que 'trimmedValue variable 'es nulo 'trimmedValue' voluntad' no es nulo Si.' ser la cadena vacía ("").

+4

Nitpick: una NullPointerException no se genera automáticamente cuando un parámetro es nulo. Solo se lanza cuando intenta desreferenciar una referencia nula (es decir, llamar algo sobre ella). –

+0

De hecho. mi mal en eso. – DwB

+0

Lo que dijo Martinho. Habiendo dicho eso, me gusta que hayas mencionado el paquete Apache Lang. Yo mismo prefiero usar sus excepciones de argumentos nulos e ilegales personalizados en lugar de lanzar directamente un NPE estándar. –

2

x == null es muy rápido, y puede ser un par de relojes de CPU (incl. 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 comprobar un registro vs un valor inmediato es rápido, la rama también se va a predecir correctamente)

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

0

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

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

Justificación: (1) que pueda bucle sobre un montón de argumentos, (2) El método nullCheck está escondido en una superclase y (3) al final del bucle,

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

en el método de la superclase, el # 3 se ve bien, pero no me lanzar una excepción (lo que es el punto, alguien tiene que manejarlo, y como contenedor de servlets, tomcat lo ignorará, por lo que podría ser este()) Re gards, - M.S.

+0

erStr + = ... agrega toneladas de sobrecarga – bestsss

+0

Lo sé, lo sé, debería haber usado un StringBuffer - ¡lo arreglaré algún día! –

+0

no, no. No habrá grandes ganancias con StringBuffer/Builder. Mi punto es que: si tienes que hacer la validación del cliente, el código está bien. Pero si realiza una comprobación nula normal, el código agrega realmente una gran cantidad de gastos generales. – bestsss

0

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 cosas 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 de lenguaje (no dice que el lenguaje es malo, pero es un defecto fundamental ... de cualquier lenguaje que permita punteros nulos o referencias.)

Como tal, lo mejor es simplemente tratarlo explícitamente 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.

0

Aunque estoy de acuerdo con el consenso general de que prefieren evitar la getClass() piratear, vale la pena señalar que a partir de OpenJDK versión 1.8.0_121, javac utilizará el getClass () hack para insertar controles nulos 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 utilizar javap para ver el código de bytes mediante la ejecución de 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 "líneas" 3, 4 y 7 están básicamente invocando o.getClass(), y descartando 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, con base en el comentario de John Rose al https://bugs.openjdk.java.net/browse/JDK-8042127?focusedCommentId=13612451&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451, sospecho que puede ser cierto que el truco getClass(), que produce una verificación nula implícita, puede ser ligeramente más eficaz que su homólogo explícito. Dicho esto, evitaría usarlo a menos que cuidadoso benchmarking mostró que hizo una diferencia apreciable.

(Curiosamente, el compilador de Eclipse para Java (TJE) hace no incluyen esta comprobación nula, y funcionando como NullCheck compilado por TJE no lanzar una NPE.)

Cuestiones relacionadas