Why is Type Inference not applied?

86 Views Asked by At

In this case, type inference works as expected, and the T type parameter handles both Integer and String:

public class A {
    public static <T> void func(T obj1, T obj2) {}
    public static void main(String[] args) {
        A.func(1, "One");
    }
}

However, the same does not seem to apply in the following example, as a compilation error occurs:

class Test<T> {
    private T object;
    public Test(T object) {
        this.object = object;
    }
}

public class A {
    public static <T> void func(Test<T> one, Test<T> two) {}
    public static void main(String[] args) {
        Test<Integer> one = new Test<>(1);
        Test<String> two = new Test<>("Two");
        A.func(one, two);  //compilation error
        //A.<Object>func(one, two);
    }
}

Even though (for example) Test<Object> could handle both Test<Integer> and Test<String>, the T type parameter isn't inferred. Why does this happen?

2

There are 2 best solutions below

0
rzwitserloot On BEST ANSWER

Even though (for example) Test could handle both Test and Test, the T type parameter isn't inferred. Why does this happen?

Nope. Wrong.

Generics is invariant. That means only the exact type will do. A subtype will not do. In contrast, 'normal' java is covariant - a subtype will do whenever one of its parent types is required.

Try it. Program along - compile this stuff and run it:

Integer i = 5;
Number n = i; // no problem

List<Integer> ints = new ArrayList<>();
List<Number> numbers = ints; // compiler error!

The reason it's a compiler error is because that is nonsense. If it was allowed (and fortunately it is not!), then.. let's continue:

Double d = 5.5;
Number dn = d; // this is fine.
numbers.add(dn); // this has to be. I add a Number to a List<Number>

But.. we just messed up. numbers and ints aren't clones of each other. They are references to the same list. It's like an address book entry: You and I have our own address books but we wrote the same address. There's 2 address books, 2 pages, both with the same address, so only one house. If you walk over to the address in your book and toss a brick through the window, and then I walk over to my address, i.. notice the broken window. Same here: That numbers.add(dn) also changes what ints sees because they reference the same list. So, it's also added to the thing ints points at which.. is a problem! We just added a Double to a List<Integer>.

This is why generics are invariant.

In your first example it 'works' because java can infer T is Object and then all works well. The second example does not work because nothing can be inferred that works - invariance means T is object will not actually work. Change it to this:

public static <T> void func(Test<? extends T> one, Test<? extends T> two) {}

Then it would work fine. Each question mark is its own thing, so, one can be different from the other one, and both Integer and String are subtypes of Object, so T now can be Object.

? extends T is basically 'I want covariance, not invariance'.

Which is fine. And the compiler will stop you from making boneheaded mistakes. Code along:

List<Integer> ints = new ArrayList<Integer>();
List<? extends Number> numbers = ints; // this DOES compile!

Number n = 5.5;
numbers.add(n); // This doesn't though!

To stop you from ruining type safety, you can't add anything to a List<? extends Whatever> (and remember that <?> is short for <? extends Object>, so also applies there). No .add() call works. Except literally .add(null) (where null is not 'some value that happens to resolve to null', no this is a compile time thing: Only literally the letters n, u, l, l - and that's only because the null literal is every type all at once).

0
Bohemian On

func requires that both Test instances passed to it have the same type (named T), but one and two have different types.

It should more clear if you use a different name for the type of func:

public static <X> void func(Test<X> one, Test<X> two) {}

The type for X is determined at the call point.

Just using the same name T as is used in Test has no effect.