ChatGPT解决这个技术问题 Extra ChatGPT

Error setting a default null value for an annotation's field

Why am I getting an error "Attribute value must be constant". Isn't null constant???

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
Because as there is no default anymore, the field becomes mandatory in annotation usage
It’s funny that no-one answered the question: “Isn’t null constant?” The answer is no, in Java, null is not a constant. null can not be used at any place where a compile-time constant is required. E.g. try to add String foo() default "" + null; to your annotation. We know, the expression predictably evaluates to "null", still, it’s not a constant expression, hence, not allowed.

H
Holger

I don't know why, but the JLS is very clear:

 Discussion

 Note that null is not a legal element value for any element type. 

And the definition of a default element is:

     DefaultValue:
         default ElementValue

Unfortunately I keep finding that the new language features (Enums and now Annotations) have very unhelpful compiler error messages when you don't meet the language spec.

EDIT: A little googleing found the following in the JSR-308, where they argue for allowing nulls in this situation:

We note some possible objections to the proposal. The proposal doesn't make anything possible that was not possible before. The programmer-defined special value provides better documentation than null, which might mean “none”, “uninitialized”, null itself, etc. The proposal is more error-prone. It's much easier to forget checking against null than to forget checking for an explicit value. The proposal may make the standard idiom more verbose. Currently only the users of an annotation need to check for its special values. With the proposal, many tools that process annotations will have to check whether a field's value is null lest they throw a null pointer exception.

I think only the last two points are relevant to "why not do it in the first place." The last point certainly brings up a good point - an annotation processor never has to be concerned that they will get a null on an annotation value. I tend to see that as more the job of annotation processors and other such framework code to have to do that kind of check to make the developers code clearer rather than the other way around, but it would certainly make it hard to justify changing it.


B
Brad Cupit

Try this

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class bar() default void.class;
}

It does not require a new class and it is already a keyword in Java that means nothing.


I like it. The only problem – in the context of the original question – is that this answer does not support type parameters: Class<? extends Foo> bar() default void.null does not compile.
The void.class can't be applied generally: public @interface NoNullDefault { String value() default void.class; }
For Groovy it works fine, even in the cases mentioned in the comments
LOL: that means nothing vs that means "nothing"
s
skaffman

It would seem this is illegal, although the JLS is very fuzzy on this.

I wracked my memory to try and think of an existing annotation out there that had a Class attribute to an annotation, and remembered this one from the JAXB API:

@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})        
public @interface XmlJavaTypeAdapter {
    Class type() default DEFAULT.class;

    static final class DEFAULT {}    
}

You can see how they've had to define a dummy static class to hold the equivalent of a null.

Unpleasant.


Yes, we do something similar (we just use Foo.class as the default). Sucks.
And in the case of String fields, you have to resort to magic strings. Apparently well-known antipatterns are better than well-understood language features now.
@TimYates It kills me to see people defining their own "no value" sentinel value when null is available and universally understood. And now that's being required by the language itself?! Luckily, you can define your "magic strings" as public constants. It's still not ideal but at least the code that's looking for your annotation can reference the constant instead of using literals everywhere.
S
Shervin Asgari

It seem there is one other way of doing it.

I don't like this either, but it might work.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo>[] bar() default {};
}

So basically you create an empty Array instead. This seems to allow a default value.


G
Gray

Class bar() default null;// this doesn't compile

As mentioned, the Java language specification does not allow null values in the annotations defaults.

What I tend to do is to do is to define DEFAULT_VALUE constants at the top of the annotation definition. Something like:

public @interface MyAnnotation {
    public static final String DEFAULT_PREFIX = "__class-name-here__ default";
    ...
    public String prefix() default DEFAULT_PREFIX;
}

Then in my code I do something like:

if (myAnnotation.prefix().equals(MyAnnotation.DEFAULT_PREFIX)) { ... }

In your case with a class, I would just define a marker class. Unfortunately you can't have a constant for this so instead you need to do something like:

public @interface MyAnnotation {
    public static Class<? extends Foo> DEFAULT_BAR = DefaultBar.class;
    ...
    Class<? extends Foo> bar() default DEFAULT_BAR;
}

Your DefaultFoo class would just an empty implementation so that the code can do:

if (myAnnotation.bar() == MyAnnotation.DEFAULT_BAR) { ... }

Hope this helps.


Incidentally, I just tried this with a String, and that doesn't work. You don't get back the same String object.
Did you use .equals() and not == @Doradus? Did you get a different value or a different object.
Different object. I used ==.
Well the class is a singleton @Doradus. I would have expected the String to be a singleton as well but I guess it isn't and you'll need to use .equals(). Surprising.
A
Alexander Terp

As others have said, there is a rule which says that null is not a valid default value in Java.

If you've got a limited number of possible values that the field can have, then one option for working around this is to make the field type a custom enum which itself contains a mapping to null. For example, imagine I have the following annotation that I want a default null for:

public @interface MyAnnotation {
   Class<?> value() default null;
}

As we've established, this is not allowed. What we can instead do is define an enum:

public enum MyClassEnum {
    NULL(null),
    MY_CLASS1(MyClass1.class),
    MY_CLASS2(MyClass2.class),
    ;

    public final Class<?> myClass;

    MyClassEnum(Class<?> aClass) {
        myClass = aClass;
    }
}

and change the annotation as such:

public @interface MyAnnotation {
  MyClassEnum value() default MyClassEnum.NULL;
}

The main downside of this (asides from needing to enumerate your possible values) is that you've now wrapped your value in an enum, so you'll need to invoke the property/getter on the enum to get the value.


S
Sotirios Delimanolis

That error message is misleading, but, no, the null literal is not a constant expression, as defined in the Java Language Specification, here.

Moreover, the Java Language Specification says

It is a compile-time error if the type of the element is not commensurate (§9.7) with the default value specified.

And to explain what that means

It is a compile-time error if the element type is not commensurate with the element value. An element type T is commensurate with an element value V if and only if one of the following is true: T is an array type E[], and either: [...] T is not an array type, and the type of V is assignment compatible (§5.2) with T, and: If T is a primitive type or String, then V is a constant expression (§15.28). If T is Class or an invocation of Class (§4.5), then V is a class literal (§15.8.2). If T is an enum type (§8.9), then V is an enum constant (§8.9.1). V is not null.

So here, your element type, Class<? extends Foo>, is not commensurate with the default value, because that value is null.


Your solution is not suitable for copy&paste ;-) Some don't like thinking, when reading
IMHO yes, you did (but it wasn't me). Your expression reads like "when (...) or (V is not null)", which sounds like any non-null value would be right. The JLS formulation is a real beast, but there's an outer or and an inner and, which can't be left out.
@maaartinus Hadn't seen your comment from that long ago, updated my answer to add the whole quote.