Reputation: 63
All, To test the Java Generics Type Inference I created below sample code.
public static <U> U addBox2(U u1, U u2) {
return u2;
}
static interface A {}
static interface B {}
static class S {}
static class C extends S implements A,B {}
static class D extends S implements B,A {}
If you see above A and S have no relationship.
In the code below -
A a = addBox2(new C(),new D());
I was hoping to receive the compilation error since the type inferred was S and I am assigning it to A and A and S has no relationship still this worked just fine.
Can someone help me explain why this behavior?
Upvotes: 2
Views: 443
Reputation: 298539
Don’t trust the tooltip of an editor that might not cope with complex generic constructs. The inferred type is S & A & B
(before Java 8), which can be assigned to A
. In Java 8, the inferred type is just A
, as generic method invocations are so-called poly expressions using the target type. For your addBox
invocation, it makes no difference.
To illustrate the issue, you may write
Object o = Arrays.asList(new C(), new D());
in Eclipse using Java 7 compliance mode, and hover the mouse over asList
. It will print
<? extends S> List<? extends S> java.util.Arrays.asList(? extends S... a)
, similar to the addBox
invocation of your question, but when you change the code to
List<A> list = Arrays.asList(new C(), new D());
you get the compiler error “Type mismatch: cannot convert from List<S&A&B>
to List<A>
” (still in Java 7 mode), showing that the actual inferred type is S & A & B
, not ? extends S
.
When you turn the language compliance level to Java 8, the compiler error will go away, as in Java 8, the target type will be used to infer the type, which will be A
instead of S & A & B
.
As said, for your addBox
invocation, it makes no difference whether A
or S & A & B
is inferred, as both can be assigned to A
, so it works under both, Java 7 and Java 8 compliance level. But when you use an invocation, where it makes a difference, like with Arrays.asList
, you can see the actually inferred type, which is never ? extends S
as the editor’s tooltip claims…
Upvotes: 0
Reputation: 48874
I suspect Eclipse and IntelliJ aren't annotating these properly. With this (slightly tweaked) example:
public static <U> U getU(U u1, U u2) {
return u2;
}
static interface A {}
static class S {}
static class C extends S implements A {}
static class D extends S implements A {}
static class T implements A {}
public static void main(String[] args) {
A a = getU(new C(), new D());
A b = getU(new C(), new T());
}
I see the following when I mouse-over the first call to getU()
in main()
:
<? extends S> ? extends S analysis.Delme.getU(? extends S u1, ? extends S u2)
Which is clearly not correct - A
does not extend S
. I suspect Eclipse is mistakenly oversimplifying the generics here, and preferring to report that C
and D
both extend from S
, rather than both implement A
.
If I mouse-over the second call I instead get the much more reasonable:
<A> A analysis.Delme.getU(A u1, A u2)
Which is presumably what the compiler is actually doing here for both calls.
Note as Thomas
points out, even if two classes have nothing in common (such as S
and T
) they still extend from Object
, so Object c = getU(new S(), new T());
is valid.
Upvotes: 0
Reputation: 88757
As requested:
A
and S
don't have a relationship indeed but since you pass a C
and D
to the method and both implement A
(and B
) they have something in common, i.e. they both are A
s.
That means the following would work:
A a = addBox2(new C(),new D()); //common type is A
B b = addBox2(new C(),new D()); //common type is B
S s = addBox2(new C(),new D()); //common type is S
Object o = addBox2(new C(),new D()); //common type is Object
As long as type inference can solve the generic type from the assignment as well as the parameters it should work (note that type inference isn't as good in Java versions prior to 8, especially in 5 and 6).
You can, however, define the type yourself by passing it to the method call:
A a = EnclosingClass.<S>addBox2(new C(),new D()); //static method within EnclosingClass
A a = this.<S>addBox2(new C(),new D()); //instance method
In both cases you define the generic type to be S
and thus the assignment won't work.
Upvotes: 2
Reputation: 1507
Type parameter U
is inferred from the type of the a
variable you are assigning the result of addBox2(...)
to, which in your case is A
. As both C
and D
implement A
it works flawlessly.
The following of course won't work:
S s = addBox2(new C(),new D());
A a = s; // compile error, type mismatch
Upvotes: 0