简体   繁体   中英

How exactly does JVM compile ternary operators? Should I be concerned about different api versions?

So, lets say I have this piece of code:

int mode = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB ? AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;

Now, lets say I run this code on some device that is pre-gingerbread.

Is there any case in which the non-available static import of AudioManager.MODE_IN_COMMUNICATION would be hit?

What I mean is, is there any scenario in which I would see a crash due to the MODE_IN_COMMUNICATION which is not available pre gingerbread being checked?

How does the ternary operator compile in Java? Does it compile these two things as ints? Does it expand the code during compilation?

A static final "variable" that is known at compile time is compiled into your code under certain circumstances. (eg every int where the compiler knows the final value)

So your code is actually just

int mode = android.os.Build.VERSION.SDK_INT >= 11 ? 3 : 2;

And any version can run that. It's not making any references to the constants that may or may not exist on the Android device.

The technical details can be found within the Java Language Specifiction eg §13.1

References to fields that are constant variables (§4.12.4) are resolved at compile time to the constant value that is denoted. No reference to such a field should be present in the code in a binary file

You can see from the documentation if something is such a constant value.

Build.VERSION.SDK_INT is itself a static final int but is not inlined at compile time. The documentation does not state a constant value

It is implemented as

public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);

and the compile can't figure out what SystemProperties.getInt will return so this value is the only one that actually references a value from within your device.

Scroll to the bottom of my answer to see what the actual javac source code does :)

The Bytecode Generated

@zapl's answer definitely answers the specific question, but I feel like the OP's question still wasn't answered. How exactly does JVM compile ternary operators? So, I just want to answer that question for everyone wondering.

We can figure this out by looking at the actual bytecode generated. So, I created a test where I have two outside classes that have some static variable I'm referencing, and all that, but that's still besides the point because we just want to know if it compiles it the same way as an if-else. Regardless, I did a test with ternary and with the equivalent if-else and these are the results.

Java Code:

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

As you can see I commented out the if-else for the test with the ternary, and then I just commented out the ternary when I was testing the if-else. The bytecode that resulted was this.

Bytecode using 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

Bytecode using the ternary:

  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

The resulting bytecode literally has just one extra instruction, which is storing the result in the first branch of the if-statement (wherease the ternary just stores the result at the end of the comparison). So, ternaries only execute the branch that would be followed according to the evaluation of the argument, just like an if-else statement.

And, because I was curious, I decided to check if a double ternary is equivalent to an if-elif-else. (Spoiler it is). I used this code to test it, same process as above:

    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;
        // }
    }

The bytecode generated for 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

The bytecode generated for ternary:

  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

What Javac Source Code Actually Does

For the bold the brave and the few

I decided to look at the source for javac... It took awhile, but with a little help from their hitchhiker's guide to javac, I was able to find the one line that definitively determines what happens. Check it out (Line 914): https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Do you see that? Let me clarify this a bit, lines 905-918 say this:

    /** 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;
        }
    }

The comment tells us this is what they use for parsing ternary expressions, and if we look at what it returns, it returns a conditional where t is the expression being evaluated, t1 is the first branch, and t2 is the second branch. Let's take a look at Conditional just to be sure. It looks like Conditional is being called from F , which if we dig a little deeper we can find out is the TreeMaker , what is a tree maker you may ask? Well, it's specifically an Abstract Syntax Tree which is often used as an intermediate representation of the code being parsed (check it out here https://en.wikipedia.org/wiki/Abstract_syntax_tree ). Anyways, if we look inside that file ( https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java ) we can see at lines 306-313 this:

    public JCConditional Conditional(JCExpression cond,
                                   JCExpression thenpart,
                                   JCExpression elsepart)
    {
        JCConditional tree = new JCConditional(cond, thenpart, elsepart);
        tree.pos = pos;
        return tree;
    }

Which further confirms exactly what we thought, that a ternary expression is compiled exactly the same as an if-else statement (otherwise known as a conditional statement) :) I encourage anyone interested to take a look at the hitchhiker's guide ( https://openjdk.java.net/groups/compiler/doc/hhgtjavac/index.html ) and the code, it's actually really interesting to see how even a commercial grade compiler follows a lot of the principle things that you learn about in your standard compiler course at college.

The Java compiler literally replaces it with an if else block. I remember reading about this in a book during Programming Fundamentals I or II.

return isValid ? foo : bar;

literally precompiles to

if(isValid) {
    return foo;
} else {
    return bar;
}

which is then compiled as normal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM