Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't the ternary operator like generic types with bounded wildcards?

Tags:

The following class defines two methods, both of which intuitively have the same functionality. Each function is called with two lists of type List<? super Integer> and a boolean value which specifies which of those lists should be assigned to a local variable.

import java.util.List;  class Example {     void chooseList1(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {         List<? super Integer> list;          if (choice)             list = list1;         else             list = list2;     }      void chooseList2(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {         List<? super Integer> list = choice ? list1 : list2;     } } 

According to javac 1.7.0_45, chooseList1 is valid while chooseList2 is not. It complains:

java: incompatible types   required: java.util.List<? super java.lang.Integer>   found:    java.util.List<capture#1 of ? extends java.lang.Object> 

I know that the rules for finding the type of an expression containing the ternary operator (… ? … : …) are pretty complex, but as far as I understand them, it chooses the most specific type to which both the second and third arguments can be converted without an explicit cast. Here, this should be List<? super Integer> list1 but it isn't.

I'd like to see an explanation of why this isn't the case, preferably with a reference of the Java Language Specification and an intuitive explanation of what could go wrong if it wasn't prevented.

like image 946
Feuermurmel Avatar asked Jan 11 '14 17:01

Feuermurmel


People also ask

Why we should not use ternary operator?

They simply are. They very easily allow for very sloppy and difficult to maintain code. Very sloppy and difficult to maintain code is bad. Therefore a lot of people improperly assume (since it's all they've ever seen come from them) that ternary operators are bad.

What is a bounded wildcard?

A bounded wildcard is one with either an upper or a lower inheritance constraint. The bound of a wildcard can be either a class type, interface type, array type, or type variable. Upper bounds are expressed using the extends keyword and lower bounds using the super keyword.

What is bounded and unbounded wildcards in generics?

both bounded and unbounded wildcards provide a lot of flexibility on API design especially because Generics is not covariant and List<String> can not be used in place of List<Object>. Bounded wildcards allow you to write methods that can operate on Collection of Type as well as Collection of Type subclasses.

What are the three conditions in a ternary operator?

The conditional (ternary) operator is the only JavaScript operator that takes three operands: a condition followed by a question mark ( ? ), then an expression to execute if the condition is truthy followed by a colon ( : ), and finally the expression to execute if the condition is falsy.


2 Answers

This answers applies to Java 7.

The Java Language Specification states the following about the conditional operator (? :)

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

In the expression

List<? super Integer> list = choice ? list1 : list2; 

T1 is List<capture#1? super Integer> and T2 is List<capture#2? super Integer>. Both of these have lower bounds.

This article goes into detail about how to calculate lub(T1, T2) (or join function). Let's take an example from there

<T> T pick(T a, T b) {     return null; }  <C, A extends C, B extends C> C test(A a, B b) {     return pick(a, b); // inferred type: Object }  void tryIt(List<? super Integer> list1, List<? super Integer> list2) {     test(list1,  list2); } 

If you use an IDE and hover over test(list1, list2), you will notice the return type is

List<? extends Object> 

This is the best that Java's type inference can do. if list1 was a List<Object> and list2 was a List<Number>, the only acceptable return type is List<? extends Object>. Because this case has to be covered, the method must always return that type.

Similarly in

List<? super Integer> list = choice ? list1 : list2; 

The lub(T1, T2) is again List<? extends Object> and its capture conversion is List<capture#XX of ? extends Object>.

Finally, a reference of type List<capture#XX of ? extends Object> can not be assigned to a variable of type List<? super Integer> and so the compiler doesn't allow it.

like image 121
Sotirios Delimanolis Avatar answered Nov 05 '22 23:11

Sotirios Delimanolis


Time goes by and Java changes. I am happy to inform you that since Java 8, probably due to the introduction of "target typing", Feuermurmels example compiles without a problem.

The current version of the relevant section of the JLS says:

Because reference conditional expressions can be poly expressions, they can "pass down" context to their operands.

...

It also allows use of extra information to improve type checking of generic method invocations. Prior to Java SE 8, this assignment was well-typed:

List<String> ls = Arrays.asList();

but this was not:

List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");

The rules above allow both assignments to be considered well-typed.

It's also interesting to note that the following, derived from Sotirios Delimanolis's code does not compile:

void tryIt(List<? super Integer> list1, List<? super Integer> list2) {     List<? super Integer> l1 = list1 == list2 ? list1 : list2; //  Works fine     List<? super Integer> l2 = test(list1,  list2); // Error: Type mismatch } 

This suggests that the information available when calculating type lower bound on the return type of test is different from that of the type of the conditional operator. Why this is the case I have no idea, it could be an interesting question in itself.

I use jdk_1.8.0_25.

like image 27
Lii Avatar answered Nov 05 '22 23:11

Lii