Method references to raw types harmful?

2020-03-15 02:54发布

问题:

The code below contains a reference to Enum::name (notice no type parameter).

public static <T extends Enum<T>> ColumnType<T, String> enumColumn(Class<T> klazz) {
    return simpleColumn((row, label) -> valueOf(klazz, row.getString(label)), Enum::name);
}

public static <T, R> ColumnType<T, R> simpleColumn(BiFunction<JsonObject, String, T> readFromJson,
        Function<T, R> writeToDb) {
 // ...
}

Javac reports a warning during compilation:

[WARNING] found raw type: java.lang.Enum missing type arguments for generic class java.lang.Enum

Changing the expression to Enum<T>::name causes the warning to go away.

However Idea flags the Enum<T>::name version with a warning that:

Explicit type arguments can be inferred

In turn Eclipse (ECJ) doesn't report any problems with either formulation.

Which of the three approaches is correct?

On one hand raw types are rather nasty. If you try to put some other type argument e.g. Enum<Clause>::name will cause the compilation to fails so it's some extra protection.

On the other hand the above reference is equivalent to e -> e.name() lambda, and this formulation doesn't require type arguments.

Enviorment:

  • Java 8u91
  • IDEA 15.0.3 Community
  • ECJ 4.5.2

回答1:

There is no such thing as a “raw method reference”. Whilst raw types exist to help the migration of pre-Generics code, there can’t be any pre-Generics usage of method references, hence there is no “compatibility mode” and type inference is the norm. The Java Language Specification §15.13. Method Reference Expressions states:

If a method or constructor is generic, the appropriate type arguments may either be inferred or provided explicitly. Similarly, the type arguments of a generic type mentioned by the method reference expression may be provided explicitly or inferred.

Method reference expressions are always poly expressions

So while you may call the type before the :: a “raw type” when it referes to a generic class without specifying type arguments, the compiler will still infer the generic type signature according to the target function type. That’s why producing a warning about “raw type usage” makes no sense here.

Note that, e.g.

BiFunction<List<String>,Integer,String> f1 = List::get;
Function<Enum<Thread.State>,String> f2 = Enum::name;

can be compiled with javac without any warning (the specification names similar examples where the type should get inferred), whereas

Function<Thread.State,String> f3 = Enum::name;

generates a warning. The specification says about this case:

In the second search, if P1, ..., Pn is not empty and P1 is a subtype of ReferenceType, then the method reference expression is treated as if it were a method invocation expression with argument expressions of types P2, ..., Pn. If ReferenceType is a raw type, and there exists a parameterization of this type, G<...>, that is a supertype of P1, the type to search is the result of capture conversion (§5.1.10) applied to G<...>;…

So in the above example, the compiler should infer Enum<Thread.State> as the parametrization of Enum that is a supertype of Thread.State to search for an appropriate method and come to the same result as for the f2 example. It somehow does work, though it generates the nonsensical raw type warning.


Since apparently, javac only generates this warning when it has to search for an appropriate supertype, there is a simple solution for your case. Just use the exact type to search:

public static <T extends Enum<T>> ColumnType<T, String> enumColumn(Class<T> klazz) {
    return simpleColumn((row, label) -> valueOf(klazz, row.getString(label)), T::name);
}

This compiles without any warning.