Tricky ternary operator in Java - autoboxing

2019-01-01 05:19发布

问题:

Let\'s look at the simple Java code in the following snippet:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

In this simplest of Java code, the temp() method issues no compiler error even though the return type of the function is int, and we are trying to return the value null (through the statement return true ? null : 0;). When compiled, this obviously causes the run time exception NullPointerException.

However, it appears that the same thing is wrong if we represent the ternary operator with an if statement (as in the same() method), which does issue a compile-time error! Why?

回答1:

The compiler interprets null as a null reference to an Integer, applies the autoboxing/unboxing rules for the conditional operator (as described in the Java Language Specification, 15.25), and moves happily on. This will generate a NullPointerException at run time, which you can confirm by trying it.



回答2:

I think, the Java compiler interprets true ? null : 0 as an Integer expression, which can be implicitly converted to int, possibly giving NullPointerException.

For the second case, the expression null is of the special null type see, so the code return null makes type mismatch.



回答3:

Actually, its all explained in the Java Language Specification.

The type of a conditional expression is determined as follows:

  • If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.

Therefore the \"null\" in your (true ? null : 0) gets an int type and then is autoboxed to Integer.

Try something like this to verify this (true ? null : null) and you will get the compiler error.



回答4:

In the case of the if statement, the null reference is not treated as an Integer reference because it is not participating in an expression that forces it to be interpreted as such. Therefore the error can be readily caught at compile-time because it is more clearly a type error.

As for the conditional operator, the Java Language Specification §15.25 “Conditional Operator ? :” answers this nicely in the rules for how type conversion is applied:

  • If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.

    Does not apply because null is not int.

  • If one of the second and third operands is of type boolean and the type of the other is of type Boolean, then the type of the conditional expression is boolean.

    Does not apply because neither null nor int is boolean or Boolean.

  • If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.

    Does not apply because null is of the null type, but int is not a reference type.

  • Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases: […]

    Applies: null is treated as convertible to a numeric type, and is defined in §5.1.8 “Unboxing Conversion” to throw a NullPointerException.


回答5:

The first thing to keep in mind is that Java ternary operators have a \"type\", and that this is what the compiler will determine and consider no matter what the actual/real types of the second or third parameter are. Depending on several factors the ternary operator type is determined in different ways as illustrated in the Java Language Specification 15.26

In the question above we should consider the last case:

Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).

This is by far the most complex case once you take a look at applying capture conversion (§5.1.10) and most of all at lub(T1, T2).

In plain English and after an extreme simplification we can describe the process as calculating the \"Least Common Superclass\" (yes, think of the LCM) of the second and third parameters. This will give us the ternary operator \"type\". Again, what I just said is an extreme simplification (consider classes that implement multiple common interfaces).

For example, if you try the following:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

You\'ll notice that resulting type of the conditional expression is java.util.Date since it\'s the \"Least Common Superclass\" for the Timestamp/Time pair.

Since null can be autoboxed to anything, the \"Least Common Superclass\" is the Integer class and this will be the return type of the conditional expression (ternary operator) above. The return value will then be a null pointer of type Integer and that is what will be returned by the ternary operator.

At runtime, when the Java Virtual Machine unboxes the Integer a NullPointerException is thrown. This happens because the JVM attempts to invoke the function null.intValue(), where null is the result of autoboxing.

In my opinion (and since my opinion is not in the Java Language Specification many people will find it wrong anyway) the compiler does a poor job in evaluating the expression in your question. Given that you wrote true ? param1 : param2 the compiler should determine right away that the first parameter -null- will be returned and it should generate a compiler error. This is somewhat similar to when you write while(true){} etc... and the compiler complains about the code underneath the loop and flags it with Unreachable Statements.

Your second case is pretty straightforward and this answer is already too long... ;)

CORRECTION:

After another analysis I believe that I was wrong to say that a null value can be boxed/autoboxed to anything. Talking about the class Integer, explicit boxing consists in invoking the new Integer(...) constructor or maybe the Integer.valueOf(int i); (I found this version somewhere). The former would throw a NumberFormatException (and this does not happen) while the second would just not make sense since an int cannot be null...



回答6:

Actually, in the first case the expression can be evaluated, since the compiler knows, that it must be evaluated as an Integer, however in the second case the type of the return value (null) can not be determined, so it can not be compiled. If you cast it to Integer, the code will compile.



回答7:

private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of auto-boxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}


回答8:

How about this:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = \"\";
        s += (true ? 1 : \"\") instanceof Integer;
        System.out.println(s);

        String t = \"\";
        t += (!true ? 1 : \"\") instanceof String;
        System.out.println(t);

    }

}

The output is true, true.

Eclipse color codes the 1 in the conditional expression as autoboxed.

My guess is the compiler is seeing the return type of the expression as Object.