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开发者_StackOverflow中文版() {
if(A != null) {
return Swapper.findSwapperToUse(A).c;
}
return B;
}
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
Your findSwapperToUse
method is returning null, and you can't do null.c
.
To make sure of this, I would change your code to read:
public Integer getAOrB() {
if(A != null) {
Swapper foundSwapper = Swapper.findSwapperToUse(A);
return foundSwapper.c;
}
return B;
}
精彩评论