为注释字段设置默认null值时出错

问题描述 投票:69回答:7

为什么会出现错误“属性值必须为常数”。 null常量不是吗?

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}
java annotations default
7个回答
57
投票

我不知道为什么,但是JLS很清楚:

 Discussion

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

并且默认元素的定义是:

     DefaultValue:
         default ElementValue

不幸的是,当您不符合语言规范时,我不断发现新的语言功能(枚举和现在的注释)具有非常无用的编译器错误消息。

编辑:一点点搜索在JSR-308中发现了following,他们在这种情况下主张允许空值:

我们注意到对该提案可能存在的反对意见。

该提案无法解决以前无法实现的任何事情。

程序员定义的特殊值比null提供更好的文档,这可能意味着“无”,“未初始化”,null本身,等等。

该建议更容易出错。忘记检查空值比忘记检查显式值要容易得多。

该建议可能会使标准习语更加冗长。当前,只有注释的用户需要检查其特殊值。有了这个提议,许多处理注释的工具将不得不检查字段的值是否为空,以免它们抛出空指针异常。

我认为只有最后两点与“为什么一开始就不这样做”有关。最后一点肯定会带来好处-注释处理器永远不必担心它们将在注释值上获得null。我倾向于看到,随着注释处理器和其他此类框架代码的工作越来越多,必须进行这种检查才能使开发人员的代码更清晰,而不是反过来,但这无疑会使更改它变得不合理。


57
投票

尝试一下

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

它不需要新的类,它已经是Java中的关键字,什么都没有。


50
投票

这似乎是非法的,尽管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等效的类。

不愉快。


9
投票

似乎还有另一种方法。

我也不喜欢这个,但是可能有用。

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

因此,基本上,您创建一个空数组。这似乎允许使用默认值。


2
投票
Class<? extends Foo> bar() default null;// this doesn't compile

如上所述,Java语言规范不允许在注释默认值中使用空值。

我倾向于做的是在注释定义的顶部定义DEFAULT_VALUE常量。类似于:

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) { ... }

希望这会有所帮助。


1
投票

该错误消息具有误导性,但不,null文字不是Java语言规范here中定义的常量表达式。

此外,Java Language Specification

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

explain what that means

如果元素类型不适当,则是编译时错误与元素值。 元素类型T与元素值V当且仅当以下条件之一为真:

  • T是数组类型E[],并且可以:
    • [...]
  • T不是数组类型,并且V的类型与T的分配兼容(第5.2节):
    • 如果T是原始类型或String,则V是常数表达式(第15.28节)。
    • 如果TClass或对Class的调用(第4.5节),则V是类文字(第15.8.2节)。
    • 如果T是枚举类型(第8.9节),则V是枚举常量(第8.9.1节)。
    • V不为空

因此,您的元素类型Class<? extends Foo>与默认值不符,因为该值为null


0
投票

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

如果该字段可以具有的有限数量的可能值,那么解决此问题的一种方法是使字段类型成为自定义枚举,该枚举本身包含对null的映射。例如,假设我有以下注释,我希望为其提供默认的null:

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

我们已经确定,这是不允许的。我们可以做的是定义一个枚举:

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

    public Class<?> myClass;

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

并这样更改注释:

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

此方法的主要缺点(除了需要枚举可能的值之外,还在于您现在将值包装在枚举中,因此您需要在枚举上调用属性/获取程序以获取该值。) >

© www.soinside.com 2019 - 2024. All rights reserved.