user3616964
user3616964

Reputation: 63

Java Generics Type Inference Confusion

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

Answers (4)

Holger
Holger

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

dimo414
dimo414

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

Thomas
Thomas

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 As.

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

Markus Benko
Markus Benko

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

Related Questions