13

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

JSR 断言提案

在其中,我们创建

最终私有静态布尔$assertionsEnabled = ClassLoader.desiredAssertionStatus(className);

接着

断言(X)
变成
if ($assertionsEnabled && !x) { 抛出 }

这很有意义;)

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

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());
}

我找不到任何文档说明他们为什么使用 NEGATIVE 测试而不是肯定测试 - 即原始提案捕获了 assertionsENABLED,现在我们使用 assertionsDISABLED。

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

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

(顺便说一句,这会导致教程不正确,这很有趣!本教程的6.2.16.2.1 ,有人在回答之前关于断言的 SO 问题时引用了该内容,因此测试错误!:)

有任何想法吗?

4

3 回答 3

20

这实际上是有原因的,而不仅仅是为了更紧凑的字节码或更快的条件执行。如果您查看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"));
    }
}

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

于 2019-12-02T04:59:00.460 回答
2

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

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

于 2013-05-31T10:09:42.507 回答
2

虽然当你查看反编译的 java 源代码时看起来有多余的工作 -你不能依赖它- 你需要查看字节码级别

查看 eclipse 编译器和 oracle javac 生成的字节码:

                    #0: getstatic Test.$assertionsDisabled
                    #3: ifne #23
                    (assertion code) #6: aload_1
                    (assertion code) #7: ldc "Fred"
                    (assertion code) #9: invokevirtual String.equals(Object)
                    (assertion code) #12: ifeq #23
                    (assertion code) #15: new AssertionError
                    (assertion code) #18: dup
                    (assertion code) #19: invokespecial AssertionError.<init>()
                    (assertion code) #22: athrow
                    #23: getstatic System.out (PrintStream)
                    #26: aload_1
                    #27: invokevirtual PrintStream.println(String)
                    #30: return

请注意字节码#3 - 它不需要反转Test.$assertionsDisabled值,它只需要执行一个单一的否定测试(即,如果它是字节码级别的否定测试或肯定测试,它没有任何区别)

总之,它正在有效地实施,并且没有执行任何多余的工作。

于 2016-09-02T02:22:58.907 回答