ChatGPT解决这个技术问题 Extra ChatGPT

Returning null as an int permitted with ternary operator but not if statement

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?

Also, int foo = (true ? null : 0) and new Integer(null) both compile fine, the second being the explicit form of autoboxing.
@Izkata the problem here is for me to understand why the compiler is trying to autobox null to Integer... That would look just like "guessing" to me or "making things work"...
...Huhm, I thought I had an answer there, as the Integer constructor (what the docs I found say is used for autoboxing) is allowed to take a String as an argument (which can be null). However, they also say that the constructor acts identically to the method parseInt(), which would throw a NumberFormatException upon getting passed a null...
@Izkata - the String argument c'tor for Integer is not an autoboxing oepration. A String can't be autoboxed to an Integer. (The function Integer foo() { return "1"; } won't compile.)
Cool, learned something new about the ternary operator!

N
Nandkumar Tekale

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.


Given the link to the Java Language Specification that you posted, which point do you think gets executed in the case of the question above? The last one (since I'm still trying to understand capture conversion and lub(T1,T2))?? Also, Is it really possible to apply boxing to a null value? Wouldn't this be like "guessing"??
´@Gevorg A null pointer is a valid pointer to every possible object so nothing bad can happen there. The compiler just assumes that null is an Integer which it then can autobox to int.
@Gevorg - See nowaq's comment and my response to his post. I think he picked the correct clause. lub(T1,T2) is the most specific reference type in common in the type hierarchy of T1 and T2. (They both share at least Object, so there is always a most specific reference type.)
@Gevorg - null is not boxed into an Integer, it is interpreted as a reference to an Integer (a null reference, but that's not a problem). No Integer object is constructed from the null, so there's no reason for a NumberFormatException.
@Gevorg - If you look at the rules for boxing conversion, and apply them to null (which is not a primitive numeric type), the applicable clause is "If p is a value of any other type, boxing conversion is equivalent to an identity conversion". So boxing conversion of null to Integer yields null, without invoking any Integer constructor.
T
Tiny

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.


I assume this is related to auto-boxing? Presumably the first return would not compile prior to Java 5, right?
@Michael that appears to be the case if you set Eclipse's compliance level to pre-5.
@Michael: this definitely looks like auto-boxing (I'm quite new to Java, and cannot make more defined statement -- sorry).
@Vlad how would the compiler end up interpreting true ? null : 0 as Integer? By autoboxing 0 first??
@Gevorg: Look here: 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. and the following text.
T
Tiny

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.


But that clause of the rules doesn't apply: the second and third operands do not have the same type.
Then the answer seems to be in the following statement: > 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).
I think that's the applicable clause. Then it tries to apply auto-unboxing in order to return an int value from the function, which causes a NPE.
@nowaq I thought this too. However, if you try to explicitly box null to Integer with new Integer(null); "Let T1 be the type that results from applying boxing conversion to S1..." you would get a NumberFormatException and this is not the case...
@Gevorg I'd think since an exception happens when doing the boxing we don't get ANY result here. The compiler is just obliged to generate code that follows the definition which it does - we just get the exception before we're done.
J
Jon Purdy

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.


If 0 is autoboxed to Integer then the compiler is executing the last case of the "ternary operator rules" as described in the Java Language Specification. If that's true, then it's hard for me to believe that it would then jump to case 3 of the same rules that is having a null and a reference type that make the return value of the ternary operator being the reference type (Integer)...
@Gevorg - Why is it hard to believe that the ternary operator is returning an Integer? That's exactly what's happening; the NPE is being generated by trying to unbox the expression value in order to return an int from the function. Change the function to return an Integer and it will return null with no problem.
@TedHopp: Gevorg was responding to an earlier revision of my answer, which was incorrect. You should ignore the discrepancy.
@JonPurdy "A type is said to be convertible to a numeric type if it is a numeric type, or it is a reference type that may be converted to a numeric type by unboxing conversion" and I don't think that null falls in this category. Also, we would then go into the "Otherwise, binary numeric promotion (§5.6.2) is applied ... Note that binary numeric promotion performs unboxing conversion (§5.1.8) ..." step to determine the return type. But unboxing conversion would generate a NPE and this happens only at runtime and not while trying to determine the ternary operator type. I'm still confused..
@Gevorg: Unboxing happens at runtime. The null is treated as though it had type int, but is actually equivalent to throw new NullPointerException(), that’s all.
T
Tiny

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...


The null in OP's original code is not boxed. The way it works is: the compiler assumes that the null is a reference to an Integer. Using the rules for ternary expression types, it decides the entire expression is an Integer expression. It then generates code to autobox the 1 (in case the condition evaluates to false). During execution, the condition evaluates to true so the expression evaluates to null. When trying to return an int from the function, the null is unboxed. That then throws a NPE. (The compiler might optimize most of this away.)
L
Lion

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.


Y
Youans
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing 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
}

J
Jon

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.


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now