¿Cuál es el significado de java.util. @ Nullable?

56

Estoy leyendo el código de Guava donde encontré la anotación java.util.@Nullableen algún código. Sé el significado de @Nullable, pero no entiendo este. En particular, no puedo encontrar una clase llamada Nullableen el paquete java.util. Por favor, que alguien me diga cuál es el significado de esto java.util.@Nullable:

public static <T> java.util.@Nullable Optional<T> toJavaUtil(
    @Nullable Optional<T> googleOptional) {
  return googleOptional == null ? null : googleOptional.toJavaUtil();
}
SetDaemon
fuente
44
@PaulRooney No lo creo. El Nullableque está hablando no parece estar en javax.annotation, no java.util.
Joseph Sible-Reinstate Monica
3
Me pregunto si esto podría ser un extraño sintaxis de Java. En particular, ¿podría esto básicamente significar public static <T> @Nullable java.util.Optional<T> toJavaUtil, pero que Java simplemente no te permite escribirlo de esa manera?
Joseph Sible-Reinstate Monica
1
@JosephSible public static <T> @Nullable java.util.Optional<T> toJavaUtilno es posible, pero no hay ninguna razón para usar el FQN Optionalcuando ya existe la importación.
Tom
66
@ Tom Hay otro Optionalen el alcance allí.
Joseph Sible-Reinstate a Monica el
2
gracias chicos, encontré esto en Checker Framework. @Nullable java.util.List The correct Java syntax to write an annotation on a fully-qualified type name is to put the annotation on the simple name part, as in java.util.@Nullable List. But, it’s usually better to add import java.util.List to your source file, so that you can just write @Nullable List.
SetDaemon

Respuestas:

37

La línea public static <T> java.util.@Nullable Optional<T> toJavaUtilse escribe así, porque el estilo habitual public static <T> @Nullable java.util.Optional<T> toJavaUtilno es válido. Esto se define en el JLS §9.7.4 :

Es un error en tiempo de compilación si una anotación de tipo T se aplica a un tipo (o cualquier parte de un tipo) en un contexto de tipo, y T es aplicable en contextos de tipo, y la anotación no es admisible.

Por ejemplo, suponga un tipo de anotación TA que se metaanota con just @Target(ElementType.TYPE_USE). Los términos @TA java.lang.Objecty java.@TA lang.Objectson ilegales porque el nombre simple al que @TA es más cercano se clasifica como un nombre de paquete. Por otro lado, java.lang.@TA Objectes legal.

La declaración de tipo de org.checkerframework.checker.nullness.qual@Nullablees:

@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})

Entonces se aplica a esta regla.

Se puede ver que esta estructura no interrumpe la ejecución, ya que el java.utilnombre del paquete y la clase Optionalse dividieron, se puede ver cuando miramos el código compilado usando javap -c [compiled class name]:

class just.a.test.Main {
  just.a.test.Main();
    Code:
       0: aload_0
       1: invokespecial #1          // Method java/lang/Object."<init>":()V
       4: return

  public static <T> java.util.Optional<T> toJavaUtil(blub.Optional<T>);
    Code:
       0: aload_0
       1: ifnonnull     8
       4: aconst_null
       5: goto          12
       8: aload_0
       9: invokevirtual #2          // Method blub/Optional.toJavaUtil:()Ljava/util/Optional;
      12: areturn
}

( blub.Optionales una clase local donde copié el código de Guava para obtener un ejemplo mínimo para descompilar / compilar)

Como puede ver, la anotación ya no existe. Es solo un marcador para el compilador para evitar una advertencia cuando el método devuelve nulo (y una pista para los lectores de código fuente), pero no se incluirá en el código compilado.


Este error del compilador también se aplica a variables como:

private @Nullable2 java.util.Optional<?> o;

Pero puede ser aceptable cuando la anotación adicionalmente obtiene el tipo de destino ElementType.FIELD, como está escrito en la misma cláusula JLS:

Si TA está adicionalmente meta-anotado con @Target(ElementType.FIELD), entonces el término @TA java.lang.Objectes legal en ubicaciones que son tanto contextos de declaración como de tipo, como una declaración de campo @TA java.lang.Object f;. Aquí, se considera que @TA se aplica a la declaración de f (y no al tipo java.lang.Object) porque TA es aplicable en el contexto de declaración de campo.

Tom
fuente
11

Al usar anotaciones, esta es la sintaxis que se usa cuando desea escribir un nombre completo para el tipo, en lugar de agregar una declaración de importación.

Citando del manual del marco del verificador :

La sintaxis correcta de Java para escribir una anotación en un nombre de tipo totalmente calificado es colocar la anotación en la parte del nombre simple, como en java.util. @ Nullable List. Pero, por lo general, es mejor agregar import java.util.List a su archivo fuente, para que pueda simplemente escribir @Nullable List.

También se menciona en la página 2 de la especificación JSR308 que se puede descargar aquí . Dice:

Aparece una anotación de tipo antes del nombre simple del tipo, como en @NonNull String o java.lang. @ NonNull String.

Kartik
fuente
6

Lo extraño aquí realmente es la sintaxis desconocida para aplicar una ElementType.TYPE_USEanotación dirigida. Si revisas los documentos de Nullable , verás el objetivo desconocido:

...
@Target(value={TYPE_USE,TYPE_PARAMETER})
<public @interface Nullable
...

Esta anotación se usa justo antes del nombre simple del tipo anotado, como en los dos siguientes:

public static <T> @Nullable Optional<T> toJavaUtil
public static <T> java.util.@Nullable Optional<T> toJavaUtil

No sabía cuál era el uso de este tipo de objetivo, así que después de una lectura rápida, llegué a este ejemplo simple, que recoge los metadatos del tipo de retorno utilizando una anotación que tiene ese objetivo:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface InterceptedReturnValue {
    boolean value() default true;
}

Y lo procesó usando:

public java.lang.@InterceptedReturnValue(true) String testMethod(String param) {
    return null;
}

public @InterceptedReturnValue(false) String testMethod(int param) {
    return null;
}

public static void main(String[] args) throws Exception {
    Method m = Main.class.getDeclaredMethod("testMethod", String.class);
    if(m.getAnnotatedReturnType().isAnnotationPresent(InterceptedReturnValue.class)) {
        InterceptedReturnValue config = m.getAnnotatedReturnType()
                .getAnnotation(InterceptedReturnValue.class);

        if(config.value()) {
            //logging return value enabled
        }
    }
}

Estoy seguro de que muchos marcos útiles, como el checkerframework, hacen el uso más apropiado de ElementType.TYPE_USE

ernest_k
fuente