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