JVM究竟如何编译三元运算符?我应该关注不同的api版本吗?

Vic*_*cVu 1 java android

所以,让我说我有这段代码:

int mode = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB ? AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
Run Code Online (Sandbox Code Playgroud)

现在,假设我在一些预姜饼的设备上运行此代码.

是否存在无法获得静态导入的AudioManager.MODE_IN_COMMUNICATION情况?

我的意思是,是否有任何情况下,我会看到一个崩溃,因为在MODE_IN_COMMUNICATION姜饼被检查之前是不可用的?

三元运算符如何用Java编译?它是否将这两个东西编译为整数?它是否在编译期间扩展代码?

zap*_*apl 6

一个static final是在编译时已知的"变量"被编译成在某些情况下你的代码.(例如int编译器知道最终值的每一个)

所以你的代码实际上就是这样

int mode = android.os.Build.VERSION.SDK_INT >= 11 ? 3 : 2;
Run Code Online (Sandbox Code Playgroud)

任何版本都可以运行.它没有对Android设备上可能存在或不存在的常量进行任何引用.

技术细节可以在Java语言规范中找到,例如§13.1

对作为常量变量的字段(第4.12.4节)的引用在编译时被解析为表示的常量值.二进制文件中的代码中不应存在对此类字段的引用

您可以从文档中看到某些内容是否为常量值.

Build.VERSION.SDK_INT本身是一个static final int但在编译时没有内联.文档没有说明常量值

它被实现为

public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);
Run Code Online (Sandbox Code Playgroud)

并且编译无法确定SystemProperties.getInt将返回什么,因此该值是唯一实际引用设备中的值的值.


小智 5

滚动到我的答案底部,看看实际的 javac 源代码做了什么:)

生成的字节码

@zapl 的回答肯定回答了具体问题,但我觉得 OP 的问题仍未得到解答。JVM 究竟是如何编译三元运算符的?所以,我只是想为每个想知道的人回答这个问题。

我们可以通过查看生成的实际字节码来弄清楚这一点。所以,我创建了一个测试,其中我有两个外部类,它们有一些我正在引用的静态变量,所有这些,但这仍然不是重点,因为我们只想知道它是否以与 if-else 相同的方式编译它. 无论如何,我用三元和等效的 if-else 做了一个测试,这些是结果。

Java代码:

class main {
    public static void main(String[] args) {
        int a = 0;
        int b = 2;
        int c = a > b ? MyBigClass.VAR_1 : MyOtherBigClass.VAR_2;
        //int c;
        // if (a > b) {
        //     c = MyBigClass.VAR_1;
        // } else {
        //     c = MyOtherBigClass.VAR_2;
        // }
    }
}

class MyBigClass {
    public static int VAR_1 = 0;
}

class MyOtherBigClass {
    public static int VAR_2 = 1;
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我用三元注释掉了测试的 if-else,然后我在测试 if-else 时才注释掉了三元。产生的字节码是这样的。

使用 if-else 的字节码:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: if_icmple     16
       9: getstatic     #2                  // Field MyBigClass.VAR_1:I
      12: istore_3
      13: goto          20
      16: getstatic     #3                  // Field MyOtherBigClass.VAR_2:I       
      19: istore_3
      20: return
Run Code Online (Sandbox Code Playgroud)

使用三元的字节码:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: if_icmple     15
       9: getstatic     #2                  // Field MyBigClass.VAR_1:I
      12: goto          18
      15: getstatic     #3                  // Field MyOtherBigClass.VAR_2:I
      18: istore_3
      19: return
Run Code Online (Sandbox Code Playgroud)

生成的字节码字面上只有一个额外的指令,它将结果存储在 if 语句的第一个分支中(而三元只在比较结束时存储结果)。因此,三元组只执行根据参数的评估将遵循的分支,就像 if-else 语句一样。

而且,因为我很好奇,我决定检查双三元是否等同于 if-elif-else。(剧透了)。我用这段代码来测试它,过程和上面一样:

    public static void main(String[] args) {
        int a = 0;
        int b = 2;
        int c = 3;
        int d = a > b ? MyBigClass.VAR_1 : a > c ? MyOtherBigClass.VAR_2 : 0;
        // int d;
        // if (a > b) {
        //     d = MyBigClass.VAR_1;
        // } else if (a > c) {
        //     d = MyOtherBigClass.VAR_2;
        // } else {
        //     d = 0;
        // }
    }
Run Code Online (Sandbox Code Playgroud)

为 if-elif-else 生成的字节码:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iconst_3
       5: istore_3
       6: iload_1
       7: iload_2
       8: if_icmple     19
      11: getstatic     #2                  // Field MyBigClass.VAR_1:I
      14: istore        4
      16: goto          35
      19: iload_1
      20: iload_3
      21: if_icmple     32
      24: getstatic     #3                  // Field MyOtherBigClass.VAR_2:I       
      27: istore        4
      29: goto          35
      32: iconst_0
      33: istore        4
      35: return
Run Code Online (Sandbox Code Playgroud)

为三元生成的字节码:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iconst_3
       5: istore_3
       6: iload_1
       7: iload_2
       8: if_icmple     17
      11: getstatic     #2                  // Field MyBigClass.VAR_1:I
      14: goto          29
      17: iload_1
      18: iload_3
      19: if_icmple     28
      22: getstatic     #3                  // Field MyOtherBigClass.VAR_2:I       
      25: goto          29
      28: iconst_0
      29: istore        4
      31: return
Run Code Online (Sandbox Code Playgroud)

Javac 源代码实际上做了什么

对于勇敢者和少数人

我决定查看 javac 的源代码......这花了一段时间,但是在他们的 javac 搭便车指南的帮助下,我能够找到明确确定会发生什么的一行。检查一下(第 914 行):https : //hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ JavacParser.java 你看到了吗?让我澄清一下,第 905-918 行是这样说的:

    /** Expression1Rest = ["?" Expression ":" Expression1]
     */
    JCExpression term1Rest(JCExpression t) {
        if (token.kind == QUES) {
            int pos = token.pos;
            nextToken();
            JCExpression t1 = term();
            accept(COLON);
            JCExpression t2 = term1();
            return F.at(pos).Conditional(t, t1, t2);
        } else {
            return t;
        }
    }
Run Code Online (Sandbox Code Playgroud)

注释告诉我们这是他们用于解析三元表达式的内容,如果我们查看它返回的内容,它会返回一个条件,其中t正在评估的表达式是哪里,t1是第一个分支,t2是第二个分支。让我们来看看Conditional只是为了确定。看起来Conditional是从 调用的F,如果我们深入挖掘一下,我们会发现是TreeMaker,你可能会问什么是造树者?嗯,它特别是一个抽象语法树,通常用作正在解析的代码的中间表示(在此处查看https://en.wikipedia.org/wiki/Abstract_syntax_tree)。无论如何,如果我们查看该文件(https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java)我们可以在第 306-313 行:

    public JCConditional Conditional(JCExpression cond,
                                   JCExpression thenpart,
                                   JCExpression elsepart)
    {
        JCConditional tree = new JCConditional(cond, thenpart, elsepart);
        tree.pos = pos;
        return tree;
    }
Run Code Online (Sandbox Code Playgroud)

这进一步证实了我们的想法,即三元表达式的编译与 if-else 语句(也称为条件语句)完全相同:) 我鼓励任何有兴趣的人查看一下搭便车指南(https:// openjdk.java.net/groups/compiler/doc/hhgtjavac/index.html ) 和代码,看到即使是商业级编译器如何遵循您在标准编译器课程中学到的很多原理,这实际上真的很有趣在大学。