Qual é o significado de java.util. @ Anulável?

56

Estou lendo o código do Goiaba, onde encontrei a anotação java.util.@Nullableem algum código. Eu sei o significado de @Nullable, mas eu não entendo este. Em particular, não consigo encontrar uma classe chamada Nullableno pacote java.util. Por favor, alguém me diga qual é o significado disso java.util.@Nullable:

public static <T> java.util.@Nullable Optional<T> toJavaUtil(
    @Nullable Optional<T> googleOptional) {
  return googleOptional == null ? null : googleOptional.toJavaUtil();
}
SetDaemon
fonte
4
@PaulRooney Acho que não. O Nullableque eles estão falando, parece haver no javax.annotation, não java.util.
Joseph Sible-Reinstate Monica
3
Gostaria de saber se isso pode ser apenas um estranho Java-sintaxe-ismo. Em particular, isso poderia significar basicamente public static <T> @Nullable java.util.Optional<T> toJavaUtil, mas o Java simplesmente não permite que você escreva dessa maneira?
Joseph Sible-Reinstate Monica
11
O @JosephSible public static <T> @Nullable java.util.Optional<T> toJavaUtilrealmente não é possível, mas não há razão para usar o FQN Optionalquando a importação já existe.
Tom
6
@ Tom Há outro Optionalescopo lá.
Joseph Sible-Reinstate Monica
2
obrigado pessoal, eu encontrei isso no 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.
precisa saber é o seguinte

Respostas:

37

A linha public static <T> java.util.@Nullable Optional<T> toJavaUtilé escrita assim, porque o estilo usual public static <T> @Nullable java.util.Optional<T> toJavaUtilé inválido. Isso é definido no JLS §9.7.4 :

É um erro em tempo de compilação se uma anotação do tipo T se aplicar a um tipo (ou qualquer parte de um tipo) em um contexto de tipo, e T for aplicável em contextos de tipo e a anotação não for admissível.

Por exemplo, suponha um tipo de anotação TA que seja meta-anotado com just @Target(ElementType.TYPE_USE). Os termos @TA java.lang.Objecte java.@TA lang.Objectsão ilegais porque o nome simples do qual @TA é o mais próximo é classificado como um nome de pacote. Por outro lado, java.lang.@TA Objecté legal.

A declaração de tipo de org.checkerframework.checker.nullness.qual@Nullableé:

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

Então, isso se aplica a esta regra.

Que essa estrutura não interrompe a execução, pois o pacote java.utile o nome da classe Optionalforam divididos, pode ser visto quando analisamos o 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.Optionalé uma classe local em que copiei o código do Guava, para obter um exemplo mínimo para descompilar / compilar)

Como você pode ver, a anotação não existe mais lá. É apenas um marcador para o compilador evitar um aviso quando o método retornar nulo (e uma dica para os leitores de código-fonte), mas ele não será incluído no código compilado.


Este erro do compilador também se aplica a variáveis ​​como:

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

Mas pode se tornar aceitável quando a anotação obtém adicionalmente o tipo de destino ElementType.FIELD, conforme escrito na mesma cláusula JLS:

Se o TA for adicionalmente meta-anotado @Target(ElementType.FIELD), o termo @TA java.lang.Objecté legal em locais que são tanto contextos de declaração quanto de tipo, como uma declaração de campo @TA java.lang.Object f;. Aqui, @TA é considerado aplicável à declaração de f (e não ao tipo java.lang.Object) porque TA é aplicável no contexto da declaração de campo.

Tom
fonte
11

Ao usar anotações, esta é a sintaxe usada quando você deseja escrever um nome completo para o tipo, em vez de adicionar uma declaração de importação.

Citando o manual da estrutura do verificador :

A sintaxe Java correta para gravar uma anotação em um nome de tipo totalmente qualificado é colocar a anotação na parte do nome simples, como em java.util. @ Nullable List. Porém, geralmente é melhor adicionar java.util.List import ao seu arquivo de origem, para que você possa escrever a @Nullable List.

Também é mencionado na página 2 da especificação JSR308, que pode ser baixada aqui . Diz:

Uma anotação de tipo aparece antes do nome simples do tipo, como em @NonNull String ou java.lang. @ NonNull String.

Kartik
fonte
6

O estranho aqui é realmente a sintaxe desconhecida para aplicar uma ElementType.TYPE_USEanotação segmentada. Se você verificar os documentos do Nullable , verá o destino desconhecido:

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

Essa anotação é usada imediatamente antes do nome simples do tipo anotado, como nos dois itens a seguir:

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

Como eu não sabia o que era esse tipo de destino, depois de uma leitura rápida, cheguei a este exemplo simples, que coleta metadados de tipo de retorno usando uma anotação com esse destino:

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

E processou 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
        }
    }
}

Tenho certeza de que muitas estruturas úteis, como o checkerframework, fazem o uso mais apropriado de ElementType.TYPE_USE

ernest_k
fonte