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


79

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeInterface {
    Class<? extends Foo> bar() default null;// this doesn't compile
}

为什么有人需要它(进行编译或解决)?在相同的“功能”已经被批准Class<? extends Foo> bar();
xerx593 '19

3
由于不再存在默认值,因此该字段在注释使用中成为必填字段
Donatello

Answers:


63

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

 Discussion

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

默认元素的定义是:

     DefaultValue:
         default ElementValue

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

编辑:一点点搜索在JSR-308中发现了以下内容,他们争辩说在这种情况下允许空值:

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

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

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

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

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

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


67

试试这个

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

它不需要新的类,它已经是Java中的关键字,没有任何意义。


8
我喜欢。在原始问题的上下文中,唯一的问题是该答案不支持类型参数: Class<? extends Foo> bar() default void.null 无法编译。
Kariem

3
void.class通常无法应用:public @interface NoNullDefault {字符串value()默认void.class; }
wjohnson 2014年

对于Groovy而言,即使在评论中提到的情况下,它也能正常工作
Vampire

LOL:没什么意思VS,意思是“无”
安德烈亚斯

54

看起来这是非法的,尽管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 2009年

7
对于String字段,您必须诉诸魔术字符串。显然,众所周知的反模式现在比众所周知的语言功能要好。
蒂姆·耶茨

3
@TimYates看到人们在定义null并被普遍理解时定义了自己的“无值”标记值,这真让我震惊。现在,语言本身需要吗?幸运的是,您可以将“魔术字符串”定义为公共常量。它仍然不是理想的,但是至少正在寻找注释的代码可以引用常量,而不是在各处使用文字。
spaaarky21 2014年

11

看来还有另一种方法可以做到。

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

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

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


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

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

我倾向于做的是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) { ... }

希望这可以帮助。


顺便说一句,我只是用String尝试了一下,但这是行不通的。您不会获得相同的String对象。
Doradus

您使用.equals()而不是==@Doradus吗?您获得了另一个或另一个对象吗?
灰色

不同的对象。我用过==
Doradus

好吧,该课程是一个单例@Doradus。我本来希望String也可以是单例,但我猜不是,您需要使用.equals()。奇怪。
灰色

1

该错误消息具有误导性,但是不,原null义不是Java语言规范(此处)中定义的常量表达式。

而且,Java语言规范

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

解释这意味着什么

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

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

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


您的解决方案不适合复制粘贴;-)有些人在阅读时不喜欢思考
Matthias M

1
恕我直言,是的,你做到了(但不是我)。您的表达式读为“当(...)(V不为null)时”,这听起来像任何非null值都是正确的。JLS公式是真正的野兽,但是有外部or和内部and,这是不能忽略的。
maaartinus

@maaartinus很久以前没有看到您的评论,更新了我的回答以添加整个报价。
Sotirios Delimanolis

1

正如其他人所说,有一条规则说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 final Class<?> myClass;

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

并按如下方式更改注释:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.