Why using diamond operator fails in Outer<String>.Inner obj2 = new Outer<>().new Inner() ? While same with Inner is OK?

117 Views Asked by At

Why using diamond operator when instanting generic Outer class (together with Inner class) gives error in Snippet 2, while snippet 1 is perfectly fine ?

I know that rare types are prohibited, but my case is not rare types - in rare types both Outer and Inner are generic, but one of them (either one) is used as raw, and the other as generic.

Snippet 1:

class Outer {
    class Inner<T> {

    }
}

class Driver {
    public static void main(String[] args) {

        Outer.Inner obj1 = new Outer().new Inner<>(); // fine !
        Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine !

    }
}

Snippet 2:

class Outer<T> {
    class Inner {

    }    
}

class Driver {    
    public static void main(String[] args) {

        Outer.Inner obj1 = new Outer<>().new Inner();  // fine !
        Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error !

    }
}

P.S. Tested on Eclipse compiler.

3

There are 3 best solutions below

2
DodgyCodeException On BEST ANSWER

You are trying to use the diamond operator to infer the type parameter:

Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error !

The JLS, section 15.9, says this about diamonds:

A class instance creation expression specifies a class to be instantiated, possibly followed by type arguments (§4.5.1) or a diamond (<>) if the class being instantiated is generic

You have two class instantiation expressions:

new Outer<>()

and

new Inner()  // or more precisely, new Outer<>().new Inner()

Towards the end of section 15.9, it distinguishes between these two expressions:

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.

Thus, the second expression is a poly expression, which means that its type depends on the assignment context; but the first expression, new Outer<>(), is a standalone expression: its type is evaluated free from any context.

Your other statement

Outer.Inner obj2 = new Outer<>().new Inner();

is fine because you are using Outer as a raw type.

1
Nir Alfasi On

Compile error:

Error:(11, 50) java: incompatible types: Outer<java.lang.Object>.Inner cannot be converted to Outer<java.lang.String>.Inner

which means that you should change:

Outer<String>.Inner obj2 = new Outer<>().new Inner()

to:

Outer<String>.Inner obj2 = new Outer<String>().new Inner()

The compiler considers new Outer<>() as raw type (defaults to Object) while it's expecting Outer<String>, hence to resolve it we need to be more specific with the generic type we're providing in the assignment.

1
Smiley On

The solution to get the code to compile is covered in alfasin's answer.

However, the reason for this is how Java does infer types. Java can only infer a type in certain cases (see documentation for more detail here, specifically the section Target Types).

Quote from these docs:

Note: It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

The problem with your snippets come down to types being inferred by target type inference and the use of the dot operator to chain methods. When using the dot operator, the result of the first method is used for the second method first (gets piped). So this changes the target type so that it is the type expected by following part after the dot. However, only the result of last method in the chain of methods is assigned to the variable type and thus the last method's generic types in your case can be inferred.

So in the first snippet, the part where the type needs to be inferred is the last method after the dot operator, so the target type for this method is the variable to which the result is going to be assigned and thus can be inferred. i.e. From your first snippet the line:

Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine !

Since 'new Outer()' returns an object that does not have a generic type, no inference need to occur. The second method new Inner<>() can continue. Thenew Inner<>() method's result here will be assigned to the variable obj2 which is declared to have the type Outer.Inner<String>, and thus can be inferred from it's target that T is to be String.

In the second snippet, the part where the type needs to be inferred is the method before the dot operator, this means that the result of the first method is what the second method is applied to.

So from your second snippet the line:

Outer<String>.Inner obj2 = new Outer<>().new Inner();

The target type of new Outer<>() is what new Inner() expects, which is Outer<T>.newInner(); Because of Type erasure and how Java generics works, this T, since it is not specified as some specific type is then seen as Object. Now the second part can continue, but the result of the second method now ends up being of type Outer<Object>.Inner, which can't be converted to the variable's assigned type.

So you need to supply a type witness for the method, i.e. new Outer<SomeMethod>(), because it does not have a proper target type in the second snippet to be able to do the type inferencing in the way you want.