This question already has answers here:
Returning null as an int permitted with ternary operator but not if statement
(8 answers)
Strange Java behaviour. Ternary operator
(1 answer)
Closed 4 years ago.
Given the following code:
boolean c = true;
boolean d = true;
boolean b = c ? null : d;
System.out.println(b);
Why does the compiler not complain here?
Variable b is a primitive datatype, shouldn't the null produce an error message like "Type mismatch: cannot convert from null to boolean"?
My best guess is, that there's some autoboxing going on?
I saw this code in a project, but I would love to know the exact reason behind this...
EDIT1:
As noted below by Mena, this code produces a NullPointer during runtime
EDIT 2:
the following form also compiles without error:
boolean c = false;
boolean d = true;
boolean b = c ? null : d;
System.out.println(b);
EDIT 3:
When trying to compile with compiler level 1.4, this will NOT compile, but produce an error:
Incompatible conditional operand types null and boolean.
So auto-boxing would make sense, as it was introduced with 1.5?
The expression on the RHS is of type Boolean and will be auto-unboxed at runtime; compile time type checking won't be affected. The unboxing will lead to a runtime exception.
This does not compile, since we can not assign the null value to a variable of primitive type
boolean e = null;
This also does not compile, although there is a decision, the compiler detects that the value is always null and the same as the previous case
boolean f = c ? null : null;
In this case, as the compiler does not know the final value of b then the code compiles, but fails to run because finally the value is null
boolean c = true;
boolean d = true;
boolean b = c ? null : d;
The types in the ternary expressions have to be of the same type, so I guess the JLS says that the types are auto-boxed in such a case (it will become Boolean); considering how little checks/optimizations javac does, this is not done here. It's interesting that intellij does complain for example, that a potential NullPointerException will be thrown.
To me, this somehow falls in the same category of:
String s = null;
if (true == true) {
}
if(s == null) {
}
and the like... they all are known at compile time for us, but not for the compiler.
A little bit un-related, but a ternary operator is far from an if statement, Holger once showed me this awesome example with promotion:
boolean b = true;
Object result = b ? Integer.valueOf(42) : Long.valueOf(12);
System.out.println(result.getClass() + " " + result); // class java.lang.Long 42
Related
This question already has answers here:
Java conditional operator ?: result type
(5 answers)
Closed 6 years ago.
I'm getting nullpointer exception if I use ternary operator.
Integer val = null;
Object res = val == null ? val : val.intValue();
But not with if else
Integer val = null;
Object res;
if( val == null ) {
res = val;
} else {
res = val.intValue();
}
Can anyone please explain why?
Thanks
Sudar
The behavior you encountered results from the rules of determining the type of the ternary conditional expression.
In your case, the type of the expression
val == null ? val : val.intValue();
is int.
This is specified by JLS 15.25. :
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.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.
Your second operand is Integer and your third operand is int, therefore the type of the expression is int.
Therefore, when val == null, val is un-boxed (i.e. val.intValue() is called for a null value) and a NullPointerException is thrown.
In your if-else expression val is not un-boxed when its value is null (since you assign it to an Object variable, so there's no NullPointerException.
That said, since you are assigning an Integer variable to an Object variable, your conditions in either of the snippets are pointless (since assigning an int to an Object variable simply boxes the int back to Integer).
You can simply assign
Object res = val;
and get the same end result without the exception.
I tripped across a really strange NullPointerException the other day caused by an unexpected type-cast in the ternary operator. Given this (useless exemplary) function:
Integer getNumber() {
return null;
}
I was expecting the following two code segments to be exactly identical after compilation:
Integer number;
if (condition) {
number = getNumber();
} else {
number = 0;
}
vs.
Integer number = (condition) ? getNumber() : 0;
.
Turns out, if condition is true, the if-statement works fine, while the ternary opration in the second code segment throws a NullPointerException. It seems as though the ternary operation has decided to type-cast both choices to int before auto-boxing the result back into an Integer!?! In fact, if I explicitly cast the 0 to Integer, the exception goes away. In other words:
Integer number = (condition) ? getNumber() : 0;
is not the same as:
Integer number = (condition) ? getNumber() : (Integer) 0;
.
So, it seems that there is a byte-code difference between the ternary operator and an equivalent if-else-statement (something I didn't expect). Which raises three questions: Why is there a difference? Is this a bug in the ternary implementation or is there a reason for the type cast? Given there is a difference, is the ternary operation more or less performant than an equivalent if-statement (I know, the difference can't be huge, but still)?
According to JLS: -
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.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion
(§5.1.7) to T, then the type of the conditional expression is T.
The problem is that:
Integer number = (condition) ? getNumber() : 0;
Forces an unboxing and reboxing of the result of getNumber(). This is because the false part of the ternary (0) is an integer, so it tries to convert the result of getNumber() to an int. Whereas the following does not:
Integer number = (condition) ? getNumber() : (Integer) 0;
This is not a bug, just the way Java chose to do things.
This is how it is supposed to work. The ternary operator is not meant to be equivalent to a regular if statement. The bodies of if and else are statements, while the parts following ? and : are expressions, that are required to evaluate to the same type.
Put another way: a = b ? c : d is not supposed to be equivalent to if (b) a = c; else a = d;. Instead, b ? c : d is an expression on its own, and the assignment of its result to a won't affect the outcome.
I have the following code,
class Foo<K> {
public <T> T createK() {
return null;
}
public void foo() throws ClassNotFoundException {
K k = (1==1)?null:createK();
}
}
However, it didn't compile. It caused the following compilation error (Oracle Java 7) on the line with the conditional operator:
Type mismatch: cannot convert from Object to K
When I rewrite the foo() method as follows,
public void foo() throws ClassNotFoundException {
K k = null;
if (1==1)
k = null;
else
k = createK();
}
Then it compiles fine. How is this caused and how can I solve it?
Although the conditional operator is not exactly same as if-else conditional, but this is a different issue.
This is a known bug with type inference of generic type parameter with conditional operator which doesn't work as expected.
The solution is to provide explicit type argument:
K k1 = (1==1) ? null : this.<K>createK();
...this would work, however with compiler warning - Dead Code (of course, due to 1 == 1 part). Note, how we need to explicit use this to invoke the method with explicit type argument. Simply doing <K>createK() won't work.
Adding the explicit type argument with method invocation - this.<K>createK(); forces the type T to be inferred as K, which is otherwise inferred as Object.
However, I suspect that your doubt is really related to the type inference. It's just a coincidence that the issue came out to be that. Even though, to get some idea about how conditional operators work in different situations, and what the type of its result is, you can schedule a visit to JLS §15.25 - Conditional Operator
No, the ternary (i.e. the ? :) is a very different beast from if ... then ... else.
The ternary is an operator and the two alternatives have to be expressions evaluating to the same type.
if ... then ... else is a convenient programming construct; the bits in between are statements not expressions.
In fact, the entire ternary construct is an expression, whereas the if ... then ... else is itself a statement. (You can assign a value to a ternary; e.g. foo = ... ? ... : ... but foo = if ... then ... else is meaningless and therefore syntatically invalid.)
Your code does not compile because the types of both sides of a conditional expression must match. There is no such requirement for the if statement - in fact, there is no requirement for the two parts to be related to each other in any way, let alone assigning the same variable.
Adding a cast fixes your conditional (demo on ideone):
K k = (1==1)?null:(K)createK();
Those operators are different.
1) In case of if ... then ... else there is no limitations of actions
2) Ternary operator. Using that both sides must have the same type.
You can fix everything with cast:
K k = (1 == 1) ? null : (K)(createK());
Using condition ? valueA : valueB operator is meaningful only if there is an Lvalue,
Lvalue = condition ? valueA : valueB
The exact meannig of this operator can be transformed to an if else statement:
int y,z;
int x= (y==0) ? y+1 : z+3
this ends up to be the same as:
if(y==0)
{
x=y+1;
}else
{
x = z+3;
}
I tripped across a really strange NullPointerException the other day caused by an unexpected type-cast in the ternary operator. Given this (useless exemplary) function:
Integer getNumber() {
return null;
}
I was expecting the following two code segments to be exactly identical after compilation:
Integer number;
if (condition) {
number = getNumber();
} else {
number = 0;
}
vs.
Integer number = (condition) ? getNumber() : 0;
.
Turns out, if condition is true, the if-statement works fine, while the ternary opration in the second code segment throws a NullPointerException. It seems as though the ternary operation has decided to type-cast both choices to int before auto-boxing the result back into an Integer!?! In fact, if I explicitly cast the 0 to Integer, the exception goes away. In other words:
Integer number = (condition) ? getNumber() : 0;
is not the same as:
Integer number = (condition) ? getNumber() : (Integer) 0;
.
So, it seems that there is a byte-code difference between the ternary operator and an equivalent if-else-statement (something I didn't expect). Which raises three questions: Why is there a difference? Is this a bug in the ternary implementation or is there a reason for the type cast? Given there is a difference, is the ternary operation more or less performant than an equivalent if-statement (I know, the difference can't be huge, but still)?
According to JLS: -
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.
If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion
(§5.1.7) to T, then the type of the conditional expression is T.
The problem is that:
Integer number = (condition) ? getNumber() : 0;
Forces an unboxing and reboxing of the result of getNumber(). This is because the false part of the ternary (0) is an integer, so it tries to convert the result of getNumber() to an int. Whereas the following does not:
Integer number = (condition) ? getNumber() : (Integer) 0;
This is not a bug, just the way Java chose to do things.
This is how it is supposed to work. The ternary operator is not meant to be equivalent to a regular if statement. The bodies of if and else are statements, while the parts following ? and : are expressions, that are required to evaluate to the same type.
Put another way: a = b ? c : d is not supposed to be equivalent to if (b) a = c; else a = d;. Instead, b ? c : d is an expression on its own, and the assignment of its result to a won't affect the outcome.
I know that we cant use assignment operator in if statements in java as we use in any other few languages.
that is
int a;
if(a = 1) { }
will give a compilation error.
but the following code works fine, how?
boolean b;
if(b = true) { }
EDIT : Is this the exception to rule that assignment cant be used in if statement.
Because the "result" of an assignment is the value assigned... so it's still a boolean expression in the second case. if expressions require the condition to be a boolean expression, which is satisfied by the second but not the first. Effectively, your two snippets are:
int a;
a = 1;
if (a) { }
and
boolean b;
b = true;
if (b) { }
Is it clear from that expansion that the second version will compile but not the first?
This is one reason not to do comparisons with true and false directly. So I would always just write if (b) instead of if (b == true) and if (!b) instead of if (b == false). You still get into problems with if (b == c) when b and c are boolean variables, admittedly - a typo there can cause an issue. I can't say it's ever happened to me though.
EDIT: Responding to your edit - assignments of all kinds can be used in if statements - and while loops etc, so long as the overall condition expression is boolean. For example, you might have:
String line;
while ((line = reader.readLine()) != null)
{
// Do something with a line
}
While I usually avoid side-effects in conditions, this particular idiom is often useful for the example shown above, or using InputStream.read. Basically it's "while the value I read is useful, use it."
For if you need an expression that evaluates to boolean. b = true evalueates to boolean but a = 1 evaluates to int as assignments always evaluate to the assigned values.
The reason the second code works okay is because it is assigning 'b' the value of true, and then comparing to see if b is true or false. The reason you can do this is because you can do assignment operators inside an if statement, AND you can compare against a boolean by itself. It would be the same as doing if(true).
In java, you don't have implicit casting. So non-boolean values or not automatically transformed to booleans.
In the first case, the result of the statements is an int, which is non-boolean, which will not work. The last case, the result is boolean, which can be evaluated in an if-statement.
The rule is not that "assignment can't be used in an if statement", but that "the condition in an if statement must be of type boolean". An assignment expression produces a value of the type being assigned, so Java only permits assignment in an if statement if you're assigning a boolean value.
This is a good reason why the style if (foo == true) should be avoided, and instead simply write if (foo).