Java 断言 - $assertionsDisabled 与 $assertionsEnabled

2022-09-04 08:02:43

java中的断言编译为添加到测试中的私有合成静态布尔值 - 该提案在这里有很好的记录:

JSR 断言提案

在其中,我们创造

final private static boolean $assertionsEnabled = ClassLoader.desiredAssertionStatus(className);

然后

assert(X)
成为
if ($assertionsEnabled && !x) { throw }

这完全有道理;)

但是,我注意到我实际得到的是

public void test1(String s) {
    assert (!s.equals("Fred"));
    System.out.println(s);
}

成为

static final /* synthetic */ boolean $assertionsDisabled;

public void test1(String s) {
    if ((!(AssertTest.$assertionsDisabled)) && (s.equals("Fred"))) {
        throw new AssertionError();
    }
    System.out.println(s);
}

static {
    AssertTest.$assertionsDisabled = !(AssertTest.class.desiredAssertionStatus());
}

我找不到任何关于为什么他们使用阴性测试而不是阳性测试的文档 - 即原始提案捕获的断言ENABLED,现在我们使用断言DISABLED。

我唯一能想到的是,这可能会(可能!)产生更好的分支预测,但这对我来说似乎是一个非常蹩脚的猜测 - Java的理念是(几乎)总是使字节码变得简单,并让JIT整理出优化。

(请注意,这不是关于断言如何工作的问题 - 我知道!:))

(顺便说一句,有趣的是,这会导致不正确的教程!本教程的6.2.1,有人在回答之前关于断言的SO问题时引用了它,使测试感觉错误!:)

有什么想法吗?


答案 1

这样做实际上是有原因的,而不仅仅是为了更紧凑的字节码或更快的条件执行。如果您查看 Java 语言规范 §14.10,您将看到以下注释:

启用在其类或接口完成初始化之前执行的断言语句。

还有一个包含初始化循环的示例:

class Bar {
    static {
        Baz.testAsserts(); 
        // Will execute before Baz is initialized!
    }
}
class Baz extends Bar {
    static void testAsserts() {
        boolean enabled = false;
        assert  enabled = true;
        System.out.println("Asserts " + 
               (enabled ? "enabled" : "disabled"));
    }
}

与 的超类一样,它必须在初始化之前进行初始化。但是,其初始值设定项在尚未初始化的类的上下文中执行断言语句,因此没有机会设置该字段。在这种情况下,该字段具有其默认值,并且一切都根据规范工作:执行断言。如果我们有一个字段,则不会执行未初始化类的断言,因此它将违反规范。BarBazBazBaz$assertionsDisabled$assertionsEnabled


答案 2

布尔值实际上是用整数实现的。人们普遍认为与零进行比较更快,但我看不出有任何理由使用禁用而不是启用。

恕我直言,由于false是布尔值的默认值,因此我尝试选择一个默认值为的标志 在这种情况下会更有意义。false$assertionsEnabled


推荐