Java断言 - $ assertionsDisabled vs $ assertionsEnabled

时间:2013-05-31 09:28:59

标签: java assert decompiler

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

JSR Assertion Proposal

在其中,我们创建

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

然后

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

我找不到任何关于他们为什么选择NEGATIVE测试而不是积极测试的文档 - 即原始提案捕获断言ENABLED,现在我们使用assertionsDISABLED。

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

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

(顺便说一下,看到这会导致错误的教程非常有趣!6.2.1 of this tutorial,有人引用回复a previous SO question on assertions会让测试错误!:)

有什么想法吗?

3 个答案:

答案 0 :(得分:18)

这样做实际上是有原因的,而不仅仅是出于更紧凑的字节码或更快的条件执行之类的原因。如果您查看Java Language Specification, §14.10,将会看到以下注释:

  

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

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

虽然在查看反编译的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值,它只需要执行一次负面测试(即它不需要&#39;如果它是字节代码级别的否定测试或肯定测试,则会有所不同)

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