Method references to raw types harmful?
Asked Answered
F

1

9

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
Fever answered 12/5, 2016 at 11:36 Comment(0)
C
7

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.

Catharine answered 12/5, 2016 at 14:20 Comment(5)
Thanks for a very comprehensive response. Just to be clear: there is no possibility to do "unsafe" operations by means of a raw ReferenceType? (Something like assigning a List<String> to a List<Long> variable via a List cast)Fever
That would require raw types within the functional signature of the target type, but then, of course, an appropriate warning should be generated there. For example, consider BiConsumer<List,Object> add=List::add; which expresses an unchecked operation, but it’s the declaration of the BiConsumer that is responsible, not the method reference.Catharine
Is this something that it makes sense for the compiler to call out, or should this be considered/filed as a bug?Chalice
@M.Justin if you still get this with an up-to-date compiler, it should be filed as bug. But I think, all recent compilers agree on not issuing a warning.Catharine
@Catharine Thanks. I encountered this error in Java 17, so it's still present. It's possible I hit a similar but different scenario; I'll test it out more when I get the opportunity.Chalice

© 2022 - 2024 — McMap. All rights reserved.