Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 7 diamond operator and initialization with the derived class

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?

like image 509
user3613844 Avatar asked May 07 '14 20:05

user3613844


2 Answers

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());
  • Java 7 first evaluates the right hand side, determines that it is a Holder<B> and gives a compile error because a Holder<B> can't be converted into a Holder<A>.
  • Java 8 goes one step further and infers that 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.

like image 191
assylias Avatar answered Nov 15 '22 06:11

assylias


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

like image 22
Vishrant Avatar answered Nov 15 '22 06:11

Vishrant