Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Java's ternary operator, can the first argument be evaluated even if the expression resulted in a false value?

I found an unusual bug in my code recently through random ad-hoc testing. So, I made a test case for it.

Here is my test case:

 SampleRequest request = new SampleRequest();
    request.setA(null);
    request.setB(null);
    assertEquals(null, request.getAOrB());

A and B are defined as java.lang.Integer types and have direct setter methods to set their values into the request.

There is also an enumeration involved. It has a primitive integer value, and a method that's used in this code. I'll post the relevant portions here:

enum Swapper {
public int c;
Swapper findSwapperToUse(final int a) {
   for(Swapper swapper : values()) {
       if(swapper.c == a) {
          return swapper;
       }
   }
   return null;
}
}

Now, here's the method that is being confusing. Invoking the test method on that method results in a NPE, but on the last line of the method.

    public class SampleRequest {
    private Integer A;
    private Integer B;

    public void setA(final Integer A) {
        this.A = A;
    }

    public void setB(final Integer B) {
        this.B = B;
    }


public Integer getAOrB() {
    return A != null ? Swapper.findSwapperToUse(A).c
         : B;
}
}

In the test, both A and B are set to null. Therefore, A != null returns false. However, I get a NullPointerException at the line number for the : B line.

My guess is that for some reason the first expression, Swapper.findSwapperToUse(A).c, is being evaluated, and therefore the A.intValue() is invoked via autoboxing, resulting in a NullPointerException on the null value. Through debugging, it's known that findSwapperToUse() is not invoked.

However, according to this questionthis should not happen: Java ternary (immediate if) evaluation

The operand expression not chosen is not evaluated for that particular evaluation of the conditional expression.

Returning a null (B) will not result in a NullPointerException - it's perfectly fine to return a null result here.

What the heck is going on?

EDIT: I forgot to add that I altered the code to avoid this by using a straight up if statement - the following code does work as expected:

public Integer getAOrB() {
    if(A != null) {
        return Swapper.findSwapperToUse(A).c;
    }
    return B;
}
like image 543
MetroidFan2002 Avatar asked Feb 07 '11 16:02

MetroidFan2002


1 Answers

I guess the problem is caused by the fact that compiler infers the type of the whole expression

A != null ? Swapper.findSwapperToUse(A).c : B

as int from the type of Swapper.c, and therefore tries to apply unboxing conversion to B.

Here is the related excerpt from the JLS, §15.25:

  • Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases:
    • ...
    • Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands. Note that binary numeric promotion performs unboxing conversion (§5.1.8) and value set conversion (§5.1.13).

You can prevent it by adding the following cast:

A != null ? (Integer) Swapper.findSwapperToUse(A).c : B
like image 67
axtavt Avatar answered Sep 27 '22 20:09

axtavt