¿Cómo usar las anotaciones @Nullable y @Nonnull de manera más efectiva?

140

Puedo ver eso @Nullabley las @Nonnullanotaciones pueden ser útiles para prevenir NullPointerExceptions pero no se propagan muy lejos.

  • La efectividad de estas anotaciones disminuye completamente después de un nivel de indirección, por lo que si solo agrega unas pocas, no se propagan muy lejos.
  • Dado que estas anotaciones no se aplican correctamente, existe el peligro de asumir que un valor marcado con @Nonnullno es nulo y, en consecuencia, no realiza comprobaciones nulas.

El siguiente código hace que un parámetro marcado con @Nonnullsea nullsin generar ninguna queja. Lanza un NullPointerExceptioncuando se ejecuta.

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

¿Hay alguna manera de hacer que estas anotaciones se apliquen más estrictamente y / o se propaguen más?

Mike Rylander
fuente
1
Me gusta la idea de @Nullableo @Nonnull, pero si valen la pena, es muy "probable que soliciten debate"
Maarten Bodewes
Creo que la forma de pasar a un mundo en el que esto causa un error o advertencia del compilador es requerir un reparto @Nonnullal llamar a un @Nonnullmétodo con una variable anulable. Por supuesto, la conversión con una anotación no es posible en Java 7, pero Java 8 agregará la capacidad de aplicar anotaciones al uso de una variable, incluidas las conversiones. Entonces, esto puede ser posible implementar en Java 8.
Theodore Murdock
1
@TheodoreMurdock, sí, en Java 8 un lanzamiento (@NonNull Integer) yes sintácticamente posible, pero un compilador no puede emitir ningún código de byte específico basado en la anotación. Para las afirmaciones de tiempo de ejecución, los métodos auxiliares pequeños son suficientes como se discutió en bugs.eclipse.org/442103 (por ejemplo, directPathToA(assertNonNull(y))), pero ten en cuenta que esto solo ayuda a fallar rápidamente. La única forma segura es realizar una comprobación nula real (más, con suerte, una implementación alternativa en la rama else).
Stephan Herrmann
1
Sería útil en esta pregunta decir de qué @Nonnully de @Nullablequé está hablando, ya que hay múltiples molestias similares (consulte esta pregunta ). ¿Estás hablando de las anotaciones en el paquete javax.annotation?
James Dunn
1
@TJamesBoone Para el contexto de esta pregunta, no importa, se trataba de cómo usar cualquiera de ellos de manera efectiva.
Mike Rylander

Respuestas:

66

Respuesta corta: supongo que estas anotaciones solo son útiles para que su IDE le advierta de posibles errores de puntero nulo.

Como se dice en el libro "Código limpio", debe verificar los parámetros de su método público y también evitar los invariantes.

Otro buen consejo es que nunca devuelve valores nulos, sino que usa el Patrón de objetos nulos .

Pedro Boechat
fuente
10
Para valores de retorno posiblemente vacios, sugiero usar el Optionaltipo en lugar de simplenull
Patrick
77
Opcional no es mejor que "nulo". Opcional # get () lanza NoSuchElementException mientras que el uso de null lanza NullPointerException. Ambos son RuntimeException sin una descripción significativa. Prefiero variables anulables.
30
44
@ 30thh ¿por qué usarías Opcional.get () directamente y no Opcional.isPresent () u Opcional.map primero?
GauravJ
77
@GauravJ ¿Por qué usaría una variable anulable directamente y no verificaría, si es nula primero? ;-)
30th
55
La diferencia entre Optionaly anulable en este caso es que Optionalcomunica mejor que este valor puede estar vacío intencionalmente. Ciertamente, no es una varita mágica y en el tiempo de ejecución puede fallar exactamente de la misma manera que la variable anulable. Sin embargo, la recepción API por programador es mejor Optionalen mi opinión.
user1053510
31

Además de su IDE que le da pistas cuando pasa nulla métodos que esperan que el argumento no sea nulo, existen otras ventajas:

  • Las herramientas de análisis de código estático pueden probar lo mismo que su IDE (por ejemplo, FindBugs)
  • Puede usar AOP para verificar estas afirmaciones

Esto puede ayudar a que su código sea más fácil de mantener (ya que no necesita nullverificaciones) y menos propenso a errores.

Uwe Plonus
fuente
9
Simpatizo con el OP aquí, porque aunque cita estas dos ventajas, en ambos casos usó la palabra "puede". Eso significa que no hay garantía de que estas verificaciones ocurran realmente. Ahora, esa diferencia de comportamiento podría ser útil para las pruebas sensibles al rendimiento que le gustaría evitar ejecutar en modo de producción, para lo cual tenemos assert. Encuentro @Nullabley @Nonnullpara ser útiles ideas, pero me gustaría más fuerza detrás de ellos, en lugar de nosotros hipótesis acerca de lo que uno puede hacer con ellos, que todavía deja abierta la posibilidad de no hacer nada con ellos.
seh
2
La pregunta es por dónde comenzar. Por el momento sus animaciones son opcionales. A veces me gustaría que no lo fueran porque en algunas circunstancias sería útil hacerlas cumplir ...
Uwe Plonus
¿Puedo preguntar a qué AOP se refiere aquí?
Chris.Zou
@ Chris.Zou AOP significa programación orientada a aspectos, por ejemplo, AspectJ
Uwe Plonus
13

Creo que esta pregunta original apunta indirectamente a una recomendación general de que todavía se necesita la verificación de puntero nulo en tiempo de ejecución, aunque se use @NonNull. Consulte el siguiente enlace:

Nuevas anotaciones de tipo de Java 8

En el blog anterior, se recomienda que:

Las anotaciones de tipo opcionales no sustituyen la validación de tiempo de ejecución Antes de las anotaciones de tipo, la ubicación principal para describir cosas como la nulabilidad o los rangos estaba en el javadoc. Con las anotaciones de tipo, esta comunicación entra en el código de bytes de una manera para la verificación en tiempo de compilación. Su código aún debe realizar la validación de tiempo de ejecución.

jonathanzh
fuente
1
Entendido, pero las comprobaciones de pelusa predeterminadas advierten que las comprobaciones nulas en tiempo de ejecución son innecesarias, lo que a primera vista parece desalentar esta recomendación.
swooby
1
@swooby Por lo general, ignoro las advertencias de pelusa si estoy seguro de que mi código es correcto. Esas advertencias no son errores.
jonathanzh
12

Compilando el ejemplo original en Eclipse en el cumplimiento 1.8 y con el análisis nulo basado en anotaciones habilitado, obtenemos esta advertencia:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Esta advertencia está redactada de forma análoga a las advertencias que se obtienen al mezclar código genérico con código heredado utilizando tipos sin formato ("conversión no verificada"). Tenemos exactamente la misma situación aquí: el método indirectPathToA()tiene una firma "heredada" en el sentido de que no especifica ningún contrato nulo. Las herramientas pueden informar fácilmente de esto, por lo que lo perseguirán por todos los callejones donde las anotaciones nulas deben propagarse pero aún no lo son.

Y cuando usas un inteligente @NonNullByDefault , ni siquiera tenemos que decir esto cada vez.

En otras palabras: si las anotaciones nulas "se propagan muy lejos" pueden depender de la herramienta que use y de cuán rigurosamente preste atención a todas las advertencias emitidas por la herramienta. Con las anotaciones nulas TYPE_USE , finalmente tiene la opción de dejar que la herramienta le advierta sobre cada NPE posible en su programa, porque la nulidad se ha convertido en una propiedad intrínseca del sistema de tipos.

Stephan Herrmann
fuente
8

Estoy de acuerdo en que las anotaciones "no se propagan muy lejos". Sin embargo, veo el error del lado del programador.

Entiendo la Nonnullanotación como documentación. El siguiente método expresa que se requiere (como condición previa) un argumento no nulo x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

El siguiente fragmento de código contiene un error. El método llama directPathToA()sin exigir que yno sea nulo (es decir, no garantiza la condición previa del método llamado). Una posibilidad es agregar una Nonnullanotación también a indirectPathToA()(propagar la condición previa). La posibilidad dos es verificar la nulidad de yin indirectPathToA()y evitar la llamada a directPathToA()cuando yes nulo.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Andres
fuente
1
La propagación de la @Nonnull que tienen indirectPathToA(@Nonnull Integer y)es mi humilde opinión, una mala práctica: tendrá que mainain la propagación de la pila de llamadas completa (por lo que si se añade un nullcheque en directPathToA(), tendrá que sustituir @Nonnullpor @Nullableen la pila de llamadas completa). Esto sería un gran esfuerzo de mantenimiento para grandes aplicaciones.
Julien Kronegg
@ La anotación no nula solo enfatiza que la verificación nula del argumento está de su lado (debe garantizar que pase un valor no nulo). No es responsabilidad del método.
Alexander Drobyshevsky
@Nonnull también es algo sensato cuando el valor nulo no tiene ningún sentido para este método
Alexander Drobyshevsky
5

Lo que hago en mis proyectos es activar la siguiente opción en la inspección del código "Condiciones constantes y excepciones":
Sugerir anotación @Nullable para métodos que posiblemente puedan devolver valores nulos e informar valores anulables pasados ​​a parámetros no anotados Inspecciones

Cuando se activa, todos los parámetros no anotados se tratarán como no nulos y, por lo tanto, también verá una advertencia en su llamada indirecta:

clazz.indirectPathToA(null); 

Para verificaciones aún más fuertes, el Checker Framework puede ser una buena opción (vea este buen tutorial .
Nota : aún no lo he usado y puede haber problemas con el compilador Jack: vea este informe de errores

TmTron
fuente
4

En Java, usaría el tipo Opcional de Guava . Al ser un tipo real, obtienes garantías de compilación sobre su uso. Es fácil omitirlo y obtener un NullPointerException, pero al menos la firma del método comunica claramente lo que espera como argumento o lo que podría devolver.

Ionuț G. Stan
fuente
16
Tienes que tener cuidado con esto. Opcional solo debe usarse donde un valor es realmente opcional, y la ausencia de ese valor se usa como puerta de decisión para una lógica adicional. He visto esto abusado con el reemplazo general de Objetos con Opcionales y los cheques nulos reemplazados por cheques de presencia que no llegan al punto.
Christopher Perry
si está apuntando a JDK 8 o más reciente, prefiera usarlo en java.util.Optionallugar de la clase Guava. Vea las notas / comparación de Guava para detalles sobre las diferencias.
AndrewF
1
"cheques nulos sustituidos por los controles de presencia, que pierde el punto" se puede elaborar, a continuación, en lo que el punto es ? Esta no es la única razón para los opcionales, en mi opinión, pero es sin duda la más grande y mejor.
scubbo
4

Si usa Kotlin, admite estas anotaciones de nulabilidad en su compilador y evitará que pase un método nulo a un método java que requiere un argumento no nulo. Aunque esta pregunta originalmente estaba dirigida a Java, menciono esta característica de Kotlin porque está específicamente dirigida a estas anotaciones de Java y la pregunta era "¿Hay alguna manera de hacer que estas anotaciones se apliquen más estrictamente y / o se propaguen más?" y esta característica hace que estas anotaciones se apliquen más estrictamente .

Clase Java usando @NotNullanotaciones

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Clase Kotlin llamando a la clase Java y pasando nulo para el argumento anotado con @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Error del compilador de Kotlin al aplicar la @NotNullanotación.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

ver: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

Mike Rylander
fuente
3
La pregunta aborda Java, según su primera etiqueta, y no Kotlin.
seh
1
@seh vea la actualización de por qué esta respuesta es relevante para esta pregunta.
Mike Rylander
2
Lo suficientemente justo. Esa es una buena característica de Kotlin. Simplemente no creo que vaya a satisfacer a los que vienen aquí para aprender sobre Java.
seh
pero el acceso myString.hashCode()seguirá arrojando NPE incluso si @NotNullno se agrega en el parámetro. Entonces, ¿qué es más específico acerca de agregarlo?
kAmol
@kAmol La diferencia aquí es que al usar Kotlin, obtendrá un error de tiempo de compilación en lugar de un error de tiempo de ejecución . La anotación es notificarle que el desarrollador debe asegurarse de que no se pase un valor nulo. Esto no impedirá que se pase un valor nulo en tiempo de ejecución, pero le impedirá escribir código que llame a este método con un valor nulo (o con una función que puede devolver nulo).
Mike Rylander
-4

Dado que la nueva característica de Java 8 Opcional ya no debe usar @Nullable o @Notnull en su propio código . Tome el siguiente ejemplo:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Podría reescribirse con:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

El uso de una opción te obliga a comprobar el valor nulo . En el código anterior, solo puede acceder al valor llamando alget método.

Otra ventaja es que el código se vuelve más legible . Con la adición de Java 9 ifPresentOrElse , la función podría incluso escribirse como:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Mathieu Gemard
fuente
Incluso con Optional, todavía hay muchas bibliotecas y marcos que usan estas anotaciones, de modo que no es factible actualizar / reemplazar todas sus dependencias con versiones actualizadas para usar Opcionales. OptionalSin embargo, puede ayudar en situaciones en las que utilice nulo dentro de su propio código.
Mike Rylander