注解字段设置默认空值时出错

90
我为什么会收到“属性值必须是常量”的错误?null不是常量吗?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}

3
因为现在不再有默认值,所以注释使用中该字段变为必填项。 - Donatello
3
有趣的是没有人回答这个问题:“null不是常量吗?” 答案是否定的,在Java中,null不是一个常量。在任何需要编译时常量的地方都不能使用null。例如,尝试在你的注解中添加String foo() default "" + null;。我们知道,该表达式可预测地评估为"null",但它不是一个常量表达式,因此不允许使用。 - Holger
7个回答

72

试试这个

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

它不需要新的类,它已经是Java中的一个关键字,意思是什么也不表示。


9
我喜欢它。唯一的问题是,在原始问题的背景下,这个答案不支持类型参数:Class<? extends Foo> bar() default void.null无法编译。 - Kariem
3
void.class 不能普遍应用于 public @interface NoNullDefault { String value() default void.class; }。 - wjohnson
对于Groovy而言,即使在评论中提到的情况下,它也可以正常工作。 - Vampire
LOL: 这意味着什么都没有 vs 这意味着“无”。 - Andreas

71

我不知道为什么,但JLS非常清晰明了:

 Discussion

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

默认元素的定义如下:

     DefaultValue:
         default ElementValue

很不幸,我发现新的语言特性(枚举和注释)在不符合语言规范时,编译器错误信息非常不友好。
编辑:在JSR-308中,他们提出了允许在这种情况下使用null的以下内容
我们注意到一些可能反对这个提案的理由。
该提案并没有使任何以前不可能的事情变得可能。
程序员定义的特殊值提供了比null更好的文档,null可能表示“无”,“未初始化”,null本身等等。
该提案更容易出错。忘记检查null比忘记检查显式值要容易得多。
该提案可能使标准习惯用法更冗长。目前,只有注释的用户需要检查其特殊值。使用该提案后,许多处理注释的工具将不得不检查字段的值是否为null,以免抛出空指针异常。
我认为只有最后两点与“为什么一开始不这样做”相关。最后一点确实提出了一个好点子 - 注释处理器永远不必担心注释值为空。我倾向于认为这更多是注释处理器和其他框架代码的工作,他们需要进行这种检查以使开发人员的代码更清晰,而不是反过来。但这肯定很难证明需要改变。

55

这似乎是不合法的,尽管JLS对此非常模糊。

我想了很久,试图想到一个已有注释,其具有指向注释的Class属性,并想起了JAXB API中的这个注释:

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

    static final class DEFAULT {}    
}
您可以看到他们不得不定义一个虚拟的静态类来保存相当于null的值。不太令人愉快。

3
好的,我们做类似的事情(我们只是使用Foo.class作为默认值)。很糟糕。 - ripper234
7
在涉及字符串字段的情况下,您必须采取魔术字符串。显然,众所周知的反模式比深入理解的语言特性更好。 - Tim Yates
3
看到人们在 null 可用且被普遍理解的情况下定义自己的“无价值”标记值,我感到很痛心。现在这种做法居然被语言本身要求?幸运的是,你可以将自己的“魔术字符串”定义为公共常量。虽然这还不够理想,但至少查找注释时的代码可以引用常量而不是在各个地方使用字面值。 - spaaarky21

13

看起来还有一种做法。

我也不喜欢这个方法,但它可能有效。

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

基本上,您需要创建一个空数组。这似乎允许设置默认值。


我看到picocli使用了这种方法。 - Oswaldo Junior

2
Class<? extends Foo> bar() default null;// this doesn't compile
如前所述,Java语言规范不允许在注释的默认值中使用null值。
我倾向于在注释定义的顶部定义一个DEFAULT_VALUE常量。像这样:
```java public @interface MyAnnotation { String value() default DEFAULT_VALUE;
static final String DEFAULT_VALUE = "default"; } ```
public @interface MyAnnotation {
    public static final String DEFAULT_PREFIX = "__class-name-here__ default";
    ...
    public String prefix() default DEFAULT_PREFIX;
}

接着在我的代码中,我会做如下的操作:

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

针对您的情况,我建议您定义一个标记类。不幸的是,您不能为此定义一个常量,因此需要执行以下操作:

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

您的DefaultFoo类只是一个空实现,以便代码可以执行以下操作:

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

希望这能有所帮助。

顺便说一下,我刚试了一下用String做这个操作,但是不行。你得不到相同的String对象。 - Doradus
你使用了 .equals() 而不是 == 吗,@Doradus?你获得了不同的 value 还是不同的对象。 - Gray
不同的对象。我使用了== - Doradus
这个类是单例的@Doradus。我本来以为String也是单例的,但看来它不是,你需要使用.equals()。令人惊讶。 - Gray

2

正如其他人所说,Java有一条规则,即null不是有效的默认值。

如果字段可能具有有限的可能值,那么解决此问题的一个选项是将字段类型设置为自定义的枚举类型,该枚举类型本身包含与null的映射。例如,假设我有以下注释,我希望默认为null:

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

正如我们已经建立的,这是不允许的。相反,我们可以定义一个枚举:

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

    public final Class<?> myClass;

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

并将注释更改为:

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

这样做的主要缺点(除了需要列举可能的值之外)是,你现在已经将值包装在枚举中,因此你需要调用枚举上的属性/获取器才能获得该值。

0

不,Java语言规范没有将null字面值/值定义为常量表达式。

当定义注释类型元素的可能默认值时,规范说明如下:

如果元素的类型与指定的默认值不相称(§9.7),则它是编译时错误。

并且要解释这意味着什么

如果元素类型与元素值不相称,则会在编译时出现错误。当且仅当以下情况之一成立时,元素类型T与元素值V相称:
- T是数组类型E[],并且: - [...] - T不是数组类型,并且V的类型可以赋值给T(§5.2),并且: - 如果T是原始类型或String,则V是常量表达式(§15.28)。 - 如果T是Class或Class的调用(§4.5),则V是类字面量(§15.8.2)。 - 如果T是枚举类型(§8.9),则V是枚举常量(§8.9.1)。 - V不为null。

所以在这里,你的元素类型 Class<? extends Foo> 与默认值不相符,因为该值为 null


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接