class A {}
class B extends A {}
class Holder<T> {
T object;
Holder(T object) {
this.object = object;
}
}
There's a Holder class to hold some object, created using generics. In the main(), when initialized using the diamond operator it doesn't compile (Java 7) with derived class passed to the Holder's constructor (required A / found B):
public static void main(String[] args) {
Holder<A> holder = new Holder<>(new B());
}
But it compiles and works if the base type is specified in the right part:
public static void main(String[] args) {
Holder<A> holder = new Holder<A>(new B());
}
Why? Doesn't diamond operator define the right part of the assignment with the same type parameters as the left side?
First observation:
Holder<B> h = new Holder<>(new B());
compiles both with Java 8 and Java 7, and both create a Holder<B>
in that scenario. So using a <>
with a constructor that takes arguments is fine.
However with:
Holder<A> h = new Holder<>(new B());
Holder<B>
and gives a compile error because a Holder<B>
can't be converted into a Holder<A>
.new Holder<A>(new B())
could actually work and does that auto-magically. This is thanks to a new feature called "target typing" - see the last section of the tutorial on type inference for an overview.In more details, the improvement in Java 8 is due to the introduction of poly expressions (emphasis mine):
The type of a standalone expression can be determined entirely from the contents of the expression; in contrast, the type of a poly expression may be influenced by the expression's target type (§5 (Conversions and Contexts)).
This is a very powerful feature of Java 8 (Java 7 only offers standalone expressions that don't take the expression context into account).
A generic class instance creation is a poly expression and JLS #15.9 explains (emphasis mine):
A class instance creation expression is a poly expression (§15.2) if it uses the diamond form for type arguments to the class, and it appears in an assignment context or an invocation context (§5.2, §5.3). Otherwise, it is a standalone expression.
Because of that new rule, Java 8 allows you to use the second form above and automatically infers that new B()
should be treated as an A
(widening reference conversion) and that you meant to create a Holder<A>
in that context.
You will get
Type mismatch: cannot convert from Holder<B> to Holder<A>
The reason being,
new Holder<>(new B());
will call constructor Holder(T object)
and it will set the type of <T>
to B
which does match to your left hand assignment Holder<A> holder
.
If you will change left hand type to Holder<B> holder
or Holder<? extends A> holder
it will work fine even without giving type in right side <>
bracket.
Remember B
is a subclass of A
, so A
can hold the reference of B
that is why
Holder<A> holder = new Holder<A>(new B());
OR
Holder<? extends A> holder = new Holder<>(new B());
is valid
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With