Java type inference with lower restricted types

Why can Java infer a common ancestor from several types with an upper limit, but not from less limited types?

In particular, consider the following examples:

static class Test { static <T> T pick(T one, T two) { return two; } static void testUpperBound() { List<? extends Integer> extendsInteger = new ArrayList<>(); // List<? extends Integer> is treated as a subclass of List<? extends Number> List<? extends Number> extendsNumber = extendsInteger; // List<? extends Number> is inferred as the common superclass extendsNumber = pick(extendsInteger, extendsNumber); } static void testLowerBound() { List<? super Number> superNumber = new ArrayList<>(); // List<? super Number> is treated as a subclass of List<? super Integer> List<? super Integer> superInteger = superNumber; // The inferred common type should be List<? super Integer>, // but instead we get a compile error: superInteger = pick(superNumber, superInteger); // It only compiles with an explicit type argument: superInteger = Test.<List<? super Integer>>pick(superNumber, superInteger); } } 
+5
source share
2 answers

I think I can explain why Java distinguishes between lower and upper restricted types.

An attempt to derive a common lower bound may fail if incompatible bounds are used, such as Integer and Long . When we use the upper bound, we can always find the common upper bound, in this case List<? extends Number> List<? extends Number> . But there is no common lower bound List<? super Integer> List<? super Integer> and List<? super Long> List<? super Long> . The only safe option in the event of such a conflict would be to return List<? extends Object> List<? extends Object> , synonymous with List<?> , which means "a List unknown type".

Now, perhaps, we could resort to this only when, in fact, there is a contradictory framework, in contrast to the case in my question. But, perhaps, it was decided to make a simple way out and not to accept a common lower limit there, unless explicitly indicated.

+1
source

I am using 1.8.0_25 and I get a compilation error. The error, however, is not that the call to choose is bad, but in the variable in which you want to add the result. Repeating an example:

 static void testLowerBound() { List<? super Number> superNumber = new ArrayList<>(); List<? super Integer> superInteger = superNumber; // this gets the error superInteger = pick(superNumber, superInteger); // this doesn't pick(superNumber, superInteger); // what happening behind is List<? extends Object> behind = pick(superNumber, superInteger); superInteger = behind; // that last line gets the same compilation error } 

If you look at how T is replaced in the call, the parameters are used as List, losing information about the lower bound.

On the conclusion: everyone? not exactly "what can be assigned ...", but "a specific type that I do not want to name, which can be assigned ...". This is important because in your example you get 3 variables, 1 for each list, and another, different, for the selection result. Now, thanks to the declaration of choice, the substitution for T must satisfy the hierarchy of parameter classes. In the first case, you will need a replacement for <# 1 extends Integer> and <# 2 extends Number>. # 2 can be double, so the best key you have is that # 3 extends the number. In the second case, you will need to replace <# 1 super Integer> and <# 2 super Number>. Now this means that # 2 can be any of Number, Object, Serializable; # 1 adds Comparable and Integer to this list. The combinations can be Number, Object (and T must be Object); or Serializable, Integer (and T may be Serializable), so the best key that it has is that T is a list of an unknown type extending Object.

Of course, he could only get to Number, but you cannot get two boundaries for the same type variable, so it should be in that

0
source

Source: https://habr.com/ru/post/1209905/


All Articles