Java type inference with lower bounded types
Asked Answered
B

2

6

Why is it that Java can infer the common ancestor of multiple upper-bounded types, but not of lower-bounded types?

More specifically, 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);
    }
}
Barolet answered 26/12, 2014 at 4:34 Comment(2)
Your test compiles for me without the explicit type argument using 1.8.0_25.Marishamariska
@Alex I'm using 1.7. Maybe it's since been fixed.Barolet
B
2

I think I can explain why Java differentiates between a lower-bounded and upper-bounded type.

Trying to infer a common lower bound can fail when incompatible bounds are used, for example Integer and Long. When we're using an upper bound, it's always possible to find some common upper bound, in this case List<? extends Number>. But there's no common lower bound of List<? super Integer> and List<? super Long>. The only safe option in case of such a conflict would be to return List<? extends Object>, synonymous with List<?>, meaning "a List of unknown type".

Now, arguably we could have resorted to that only when there actually are conflicting bounds, as opposed to the case in my question. But maybe it was decided to take the easy way out and not assume there's a common lower bound unless explicitly specified.

Barolet answered 26/12, 2014 at 21:29 Comment(0)
L
0

I'm using 1.8.0_25 and I'm getting the compile error. The error, however, is not that the call to pick is bad, but the variable you want to put the result into. Repeating your 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's 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 being substituted in the call, the parameters are used as List, losing the information about the lower bound.

About inference: Every ? is not exactly "whatever that can be assigned to..." but "a particular type I don't want to name, that can be assigned to...". It matters because in your example you get 3 variables, 1 for each list and another, different one, for the result of pick. Now, due to the declaration of pick, the substitution for T has to satisfy the class hierarchy of the parameters. In the first case you need a substitute for <#1 extends Integer> and <#2 extends Number>. #2 could be Double, so the best clue you've got is that #3 extends Number. In the second case you need a substitute for <#1 super Integer> and <#2 super Number>. Now that means #2 could be anyone of Number, Object, Serializable; #1 adds to that list Comparable and Integer. The combinations could be Number, Object (and T should be Object); or Serializable, Integer (and T could be Serializable), so the best clue it has is that T is a List of an unknown type extending Object.

Of course it could only reach to Number, but you can't get two bounds for the same type variable, so has to let it at that

Lamkin answered 26/12, 2014 at 14:17 Comment(1)
I don't see your reasoning. Just like we shift the bound to the nearest common superclass (Number) with extends, we should shift it to the nearest common subclass (Integer) with super.Barolet

© 2022 - 2024 — McMap. All rights reserved.