简体   繁体   中英

Is it redundant with `TYPE_USE` to declare other `TYPE` targets?

Consider this declaration of an annotation type Foo :

import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

@Target({ ANNOTATION_TYPE, TYPE, TYPE_PARAMETER, TYPE_USE })
public @interface Foo {}

Here I think ANNOTATION_TYPE is made redundant by TYPE , but I wonder what is made redundant by TYPE_USE . The API has this to say:

The constant TYPE_USE corresponds to the type contexts in JLS 4.11 , as well as to two declaration contexts: type declarations (including annotation type declarations) and type parameter declarations.

For example, an annotation whose type is meta-annotated with … TYPE_USE may be written on the type of a field … and may also appear as a modifier for, say, a class declaration.

The JLS says much the same.

Does this mean that target TYPE_USE makes each of the other targets of Foo redundant? Or what would change if I dropped them from the declaration?

No. On the contrary - they almost (but not quite) are mutually exclusive with TYPE_USE.

TYPE_USE refers to the concept of annotating a, well, usage of a type someplace. Whereas TYPE refers to annotating a type.

Example of putting an annotation on a thing in the sense of TYPE:

@MyAnno class ThisDefinesAClass {}

Example of putting an annotation on a thing in the sense of TYPE_USE:

public void example(List<@ThisIsTypeUse String> names) {}

Note how the TYPE_USE annotation is associated with the USAGE of a type, not the definition of a type. We aren't defining String. Merely using it.

Where it gets a little nutty is in something like this:

public @ThisIsUhWhatIsThis String example() {}

This is ambigious: Are you annotating the definition of the example method, or, are you annotating the usage of String , as part of the return type of the example method?

If your annotation is specced to allow TYPE_USE only, then this is considered as annotating the String part. If the annotation is specced to allow METHOD, then this is considered to be annotating the entire method.

If it's specced to allow BOTH , then it's a weird mishmash of rules that requires reviewing the JLS every time because they are neccessarily weird and confusing. Thus, it is almost always a good idea NOT to mix TYPE_USE with anything else.

Sometimes the definitions boil down to the same thing, but usually TYPE_USE is the better way to think about it. Example:

public @NonNull String getFoo() {return foo;}

This annotation is ambiguous. However, both interpretations lead to the same conclusion:

  • This annotation means that the entire method 'getFoo' has been annotated as a 'NonNull' method. Whilst one can conjure up any number of things as to what this might mean, if I tell you that it means: "It will never return null", I'm sure you'd find that a completely plausible thing (and it is, in fact, what @NonNull annotations that predate the existence of TYPE_USE mean, and for presumably backwards compat reasons, a lot of nonnull annotations still work like that: They target methods and params and fields, not TYPE_USE).

  • The alternative interpretation says that this annotation means that the use of 'String' as return type is so annotated: This is a method. Its return type is @NonNull String , as in, the type String , but tagged as @NonNull . This means, of course, the same thing: This method cannot return null. But this way of thinking makes more sense. For example, the old way means you simply cannot communicate the notion of 'the first parameter of this method must either be a null pointer, or a pointer to some instance of List, which is constrained to contain only Strings. Those pointers to Strings that the list has must ALL be non-null. That'd be @Nullable List<@NonNull String> , and you need TYPE_USE to express concepts like this.

Yes, given a target of TYPE_USE , nothing changes if you add TYPE , ANNOTATION_TYPE or TYPE_PARAMETER to the list of targets. Targeting TYPE_USE makes all other 'TYPE' targets redundant.

During the public review of the JSR that introduced the TYPE_USE constant, co-lead Alex Buckley wrote :

The Foo type has a TYPE_USE target, so @Foo can appear in at least the places permitted by a TYPE target, which includes declarations of annotation types.

And again , in the same thread:

JSR 308 introduced ElementType.TYPE_USE as pertaining not only to uses of types but also to declarations of types; logically, it's a "supertype" of ElementType.TYPE .

Recall that ElementType defines its constants each as a class of 'syntactic locations… where it is legal to write annotations'. Taking Alex at his word, then, TYPE_USE is not specific to locations of use, but a catch-all for locations of declaration and use. Logically a TYPE location is a TYPE_USE location. The same logic applies to the other 'TYPE' constants, which means these two declarations of Foo are equivalent:

@Target({ TYPE_USE, TYPE, ANNOTATION_TYPE, TYPE_PARAMETER })
public @interface Foo {} // is the same as:

@Target({ TYPE_USE })
public @interface Foo {}

It might help to think of TYPE_USE as a superset of the other 'TYPE' locations. Alex might even have meant to say 'superset' instead of 'supertype'. Either way, the conclusion would be the same: TYPE_USE alone covers all the other 'TYPE' locations.

It would certainly help if the constants could somehow be renamed. A catch-all by the name of 'TYPE' (instead of 'TYPE_USE') together with 'TYPE_DECLARATION' (instead of 'TYPE'), and so forth, would make things much clearer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM