One of the boons introduced with Java 7 is the so-called diamond operator <>
.
And it has been with us for so long, that it's easy to forget that every time when diamond is being used while instantiating a generic class the compiler should infer the generic type from the context.
If we define a variable which will hold a reference to a list of Person
objects like this:
List<Person> people = new ArrayList<>(); // effectively - ArrayList<Person>()
the compiler will infer the type of the ArrayList
instance from the type of the variable people
on the left.
In the Java language specification, the expression new ArrayList<>()
is being described as a class instance creation expression and because it doesn't specify the generic type parameter and is used within a context, it should be classified as being a poly expression. A quote from the specification:
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).
I.e. when diamond <>
is used with a generic class instantiation, the actual type will depend on the context in which it appears.
The three statements below represent the case of so-called assignment context. And all three instances Container
will be inferred as being of type B
.
Container<B> conta = new Container<>(new A()); // 1 - ERROR because `B t = new A()` is incorrect
Container<B> contb = new Container<>(new B()); // 2 - fine because `B t = new B()` is correct
Container<B> contc = new Container<>(new C()); // 3 - fine because `B t = new C()` is also correct
Since all instances of container are of type B
and of parameter type expected by the contractor also will be B
. I.e. can provide an instance of B
or any of its subtypes. Therefore, in the case 1
we are getting a compilation error, meanwhile 2
and 3
(B
and subtype of B
) will compile correctly.
And it in't a violation of invariant behavior. Think about it this way: we can store in a List<Number>
instances of Integer
, Byte
, Double
, etc., that would not lead to any problem since they all can represent their super type Number
. But the compiler will not allow assigning this list to any list that is not of type List<Number>
because otherwise it would be impossible to ensure that this assignment is safe. And that is what the invariance means - we can assign only List<Number>
to a variable of type List<Number>
(but we are free to store any subtype of Number
in it, it's safe).
As an example, let's consider that there's a setter method in the Container
class:
public class Container<T> {
public T t;
public Container(T t) {
this.t = t;
}
public void setT(T t) {
this.t = t;
}
}
Now let's use it:
Container<B> contb = new Container<>(null); // to avoid any confusion initialy `t` will be assigned to `null`
contb.setT(new A()); // compilation error - because expected type is `B` or it's subtype
contb.setT(new B()); // fine
contb.setT(new C()); // fine because C is a subtype of B
When we deal with a class instance creation expression using diamond <>
, which is passed to a method as an argument, the type will be inferred from the invocation context as the quote from the specification provided above states.
Because method()
expects Container<B>
, all instances above will be inferred as being of type B
.
method(new Container<>(new A())); // Error
method(new Container<>(new B())); // OK - because `B t = new B()` is correct
method(new Container<>(new C())); // OK - because `B t = new C()` is also correct
Note
The important thing to mention that prior to Java 8 (i.e. with Java 7, because we are using diamond) the expression new Container<>(new C())
will be interpreted by the compiler as a standalone expression (i.e. the context will be ignored) creating an instance of Container<C>
. It means your initial guess was somewhat correct: with Java 7 the below statement would not compile.
Container<B> contc = new Container<>(new C()); // Container<B> = Container<C> - is an illegal assignment
But Java 8 has introduced a feature called target types and poly expressions (i.e. expressions that appear within a context) that insures that context will always be taken into account by the type inference mechanism.