Jakie jest znaczenie java.util. @ Nullable?

56

Czytam kod z Guawy, w którym znalazłem adnotację java.util.@Nullablew jakimś kodzie. Znam znaczenie @Nullable, ale nie rozumiem tego. W szczególności nie mogę znaleźć klasy o nazwie Nullablew pakiecie java.util. Proszę, ktoś powie mi, co to oznacza java.util.@Nullable:

public static <T> java.util.@Nullable Optional<T> toJavaUtil(
    @Nullable Optional<T> googleOptional) {
  return googleOptional == null ? null : googleOptional.toJavaUtil();
}
SetDaemon
źródło
4
@PaulRooney Nie sądzę. NullableMówią o nie wydaje się być w javax.annotationnie java.util.
Joseph Sible-Reinstate Monica
3
Zastanawiam się, czy to może być dziwny syntezator Java. W szczególności, czy to może po prostu oznaczać public static <T> @Nullable java.util.Optional<T> toJavaUtil, ale ta Java po prostu nie pozwala Ci pisać w ten sposób?
Joseph Sible-Reinstate Monica
1
@JosephSible public static <T> @Nullable java.util.Optional<T> toJavaUtilrzeczywiście nie jest możliwe, ale nie ma powodu, aby używać nazwy FQN, Optionalgdy import już istnieje.
Tom
6
@Tom Jest jeszcze inny Optionalzakres.
Joseph Sible-Reinstate Monica
2
dzięki chłopaki, znalazłem to w 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,

Odpowiedzi:

37

Linia public static <T> java.util.@Nullable Optional<T> toJavaUtiljest napisana w ten sposób, ponieważ zwykły styl public static <T> @Nullable java.util.Optional<T> toJavaUtiljest nieprawidłowy. Jest to zdefiniowane w JLS §9.7.4 :

Jest to błąd czasu kompilacji, jeśli adnotacja typu T dotyczy typu (lub dowolnej części typu) w kontekście typu, a T ma zastosowanie w kontekstach typu, a adnotacja nie jest dopuszczalna.

Na przykład załóżmy, że adnotacja typu TA ma meta-adnotację za pomocą just @Target(ElementType.TYPE_USE). Warunki @TA java.lang.Objecti java.@TA lang.Objectsą nielegalne, ponieważ prosta nazwa, do której @TA jest najbliżej, jest klasyfikowana jako nazwa pakietu. Z drugiej strony java.lang.@TA Objectjest legalne.

Deklaracja typu org.checkerframework.checker.nullness.qual@Nullableto:

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

Dotyczy to tej reguły.

Że ta struktura nie przerywa wykonania, ponieważ java.utilnazwa pakietu i klasy Optionalzostały podzielone, można zobaczyć, gdy spojrzymy na skompilowany kod za pomocą 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.Optionaljest klasą lokalną, do której skopiowałem kod Guava, aby uzyskać minimalny przykład do de- / kompilacji)

Jak widać, adnotacja już tam nie istnieje. Jest to tylko znacznik kompilatora, który zapobiega ostrzeżeniu, gdy metoda zwraca null (i wskazówka dla czytników kodów źródłowych), ale nie zostanie uwzględniony w skompilowanym kodzie.


Ten błąd kompilatora dotyczy również zmiennych takich jak:

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

Ale może stać się akceptowalny, gdy adnotacja dodatkowo uzyska typ docelowy ElementType.FIELD, jak napisano w tej samej klauzuli JLS:

Jeśli TA jest dodatkowo meta-adnotacja @Target(ElementType.FIELD), to termin @TA java.lang.Objectjest legalny w lokalizacjach, które są zarówno kontekstami deklaracji, jak i typów, takich jak deklaracja pola @TA java.lang.Object f;. W tym przypadku uważa się, że @TA ma zastosowanie do deklaracji f (a nie do typu java.lang.Object), ponieważ TA ma zastosowanie w kontekście deklaracji pola.

Tomek
źródło
11

W przypadku korzystania z adnotacji jest to składnia używana w celu zapisania w pełni kwalifikowanej nazwy typu zamiast dodawania instrukcji importu.

Cytowanie z podręcznika ram kontrolera :

Prawidłową składnią Java do napisania adnotacji na w pełni kwalifikowanej nazwie typu jest umieszczenie adnotacji na części prostej nazwy, jak w java.util. @ Nullable List. Ale zwykle lepiej jest dodać import java.util.List do pliku źródłowego, aby można było po prostu napisać @Nullable List.

Jest również wspomniany na stronie 2 specyfikacji JSR308, którą można pobrać tutaj . To mówi:

Adnotacja typu pojawia się przed prostą nazwą typu, jak w @NonNull String lub java.lang. @ NonNull String.

Kartik
źródło
6

Dziwny jest tutaj nieznana składnia do zastosowania ElementType.TYPE_USEukierunkowanej adnotacji. Jeśli sprawdzisz dokumenty Nullable , zobaczysz nieznany cel:

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

Ta adnotacja jest używana tuż przed prostą nazwą typu adnotacji, jak w obu poniższych przypadkach:

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

Nie wiedziałem, do czego służy ten cel, więc po szybkim przeczytaniu doszedłem do tego prostego przykładu, w którym przy pomocy adnotacji, która ma ten cel, wychwytuje metadane typu zwracanego:

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

I przetworzył to za pomocą:

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

Jestem pewien, że wiele użytecznych frameworków, takich jak checkerframework, najlepiej wykorzystuje ElementType.TYPE_USE

ernest_k
źródło