Difference between C# and Java's ternary operator (? :) Difference between C# and Java's ternary operator (? :) java java

Difference between C# and Java's ternary operator (? :)


Looking through the C# 5 Language Specification section 7.14: Conditional Operator we can see the following:

  • If x has type X and y has type Y then

    • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

    • If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

    • Otherwise, no expression type can be determined, and a compile-time error occurs

In other words: it tries to find whether or not x and y can be converted to eachother and if not, a compilation error occurs. In our case int and string have no explicit or implicit conversion so it won't compile.

Contrast this with the Java 7 Language Specification section 15.25: Conditional Operator:

  • 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. (NO)
  • 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. (NO)
  • 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. (NO)
  • Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases: (NO)
  • 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). (YES)

And, looking at section 15.12.2.7. Inferring Type Arguments Based on Actual Arguments we can see it tries to find a common ancestor that will serve as the type used for the call which lands it with Object. Object is an acceptable argument so the call will work.


The given answers are good; I would add to them that this rule of C# is a consequence of a more general design guideline. When asked to infer the type of an expression from one of several choices, C# chooses the unique best of them. That is, if you give C# some choices like "Giraffe, Mammal, Animal" then it might choose the most general -- Animal -- or it might choose the most specific -- Giraffe -- depending on the circumstances. But it must choose one of the choices it was actually given. C# never says "my choices are between Cat and Dog, therefore I will deduce that Animal is the best choice". That wasn't a choice given, so C# cannot choose it.

In the case of the ternary operator C# tries to choose the more general type of int and string, but neither is the more general type. Rather than picking a type that was not a choice in the first place, like object, C# decides that no type can be inferred.

I note also that this is in keeping with another design principle of C#: if something looks wrong, tell the developer. The language does not say "I'm going to guess what you meant and muddle on through if I can". The language says "I think you've written something confusing here, and I'm going to tell you about that."

Also, I note that C# does not reason from the variable to the assigned value, but rather the other direction. C# does not say "you're assigning to an object variable therefore the expression must be convertible to object, therefore I will make sure that it is". Rather, C# says "this expression must have a type, and I must be able to deduce that the type is compatible with object". Since the expression does not have a type, an error is produced.


Regarding the generics part:

two > six ? new List<int>() : new List<string>()

In C#, the compiler tries to convert the right-hand expression parts to some common type; since List<int> and List<string> are two distinct constructed types, one can't be converted to the other.

In Java, the compiler tries to find a common supertype instead of converting, so the compilation of the code involves the implicit use of wildcards and type erasure;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

has the compile type of ArrayList<?> (actually, it can be also ArrayList<? extends Serializable> or ArrayList<? extends Comparable<?>>, depending on use context, since they are both common generic supertypes) and runtime type of raw ArrayList (since it's the common raw supertype).

For example (test it yourself),

void test( List<?> list ) {    System.out.println("foo");}void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here                                 // since both test() methods would clash after the erasure    System.out.println("bar");}void test() {    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo     test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar} // compiler automagically binds the correct generic QED