I developed some code in Eclipse, tested it successfully, pushed it to our Jenkins CI server, and got an email that Maven was choking with a Java compile error. I subsequently isolated the problem and created the following minimal example showing the issue:
import java.util.List;
import java.util.function.Function;
class MinimalTypeFailureExample {
public static void main(String[] args) {
List<String> originalList = null; // irrelevant
List<IntToByteFunction> resultList = transform(originalList,
outer -> inner -> doStuff(inner, outer));
System.out.println(resultList);
}
static <F, T> List<T> transform(List<F> originalList,
MyFunction<? super F, ? extends T> function) {
return null; // irrelevant
}
static Byte doStuff(Integer inner, String outer) {
return null; // irrelevant
}
}
@FunctionalInterface
interface MyFunction<F, T> extends Function<F, T> {
@Override
T apply(F input);
}
@FunctionalInterface
interface IntToByteFunction {
Byte applyIntToByte(Integer inner);
}
In Eclipse, this code compiles without error and appears to execute as intended. However, compiling with javac gives the following error:
MinimalTypeFailureExample.java:7: error: incompatible types: cannot infer type-variable(s) F,T
List<IntToByteFunction> resultList = transform(originalList, outer -> inner -> doStuff(inner, outer));
^
(argument mismatch; bad return type in lambda expression
T is not a functional interface)
where F,T are type-variables:
F extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
T extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
1 error
Changing the argument type of transform()
from MyFunction
to Function
, or removing the wildcard ? extends
in the argument type, makes the example code compile in javac.
Clearly, either Eclipse or javac is in violation of the Java Language Specification. The question is, do I file the bug report on Eclipse or javac? The type inference rules for generic lambdas are so complex that I have no idea whether this program is legal Java or not according to the JLS.
Motivation note
In the original code,
transform()
was Guava'scom.google.common.collect.Lists.transform()
. TheMyFunction
interface was Guava 'scom.google.common.base.Function
interface, which extendsjava.util.function.Function
for historical reasons.The purpose of this code was to create a view of a list of a first type as a list of a second type. The second type was a functional interface type and I wanted to populate the output list with functions of this type constructed based on the values in the input list—hence the curried lambda expression.
Version info for reproducibility
Eclipse versions tested:
- 2018-09 (4.9.0) Build id: 20180917-1800
- 2019-03 RC1 (4.11 RC1) Build id: 20190307-2044
javac versions tested:
- 1.8.0_121
- JDK 10.0.1 via the JDoodle online Java compiler
It looks like you run into JDK bug JDK-8156954 which has been fixed in Java 9 but not in Java 8.
It is a bug of Java 8 javac
because in your example all variable types of the transform
method can be inferred without violating the Java language specification as follows:
F
: String
(via first parameter originalList
of type List<String>
)T
: IntToByteFunction
(via return type List<IntToByteFunction>
)These inferred variable types are compatible with the type of the second parameter, the chained lambda expression:
outer -> inner -> doStuff(inner, outer)
resolves (with doStuff(Integer, String)
toString -> Integer -> doStuff(Integer, String)
resolves toString -> Integer -> Byte
is compatible withString -> IntToByteFunction
is compatible withMyFunction<? super String, ? extends IntToByteFunction>
Your example can be minimized further:
import java.util.function.Function;
class MinimalTypeFailureExample {
void foo() {
transform((Function<Integer, String>)null, o -> i -> {return "";});
}
<T, F> void transform(F f, MyFunction<T, ? extends F> m) {}
}
@FunctionalInterface
interface MyFunction<T, R> extends Function<T, R> {
@Override
R apply(T t);
}
MyFunction
overrides the same with the same (R apply(T t);
). If Function
instead of MyFunction
is used or if MyFunction
extends Function
but without @Override R apply(T t);
then the error disappears. Also with F
instead of ? extends F
the error disappears.
Even if your example differs from the example in the mentioned bug, it can be assumed that it is the same bug because it is the only "argument mismatch; bad return type in lambda expression bug that has been fixed in Java 9 but not in Java 8 and that occurs only with lambda functions in combination with Java Generics.
I tried the example code with javac 11.0.2 and received no error. That would suggest that the bug may have been in javac and is fixed in recent versions. I am slightly surprised at this because as mentioned I did try testing JDK 10 in an online interface.
I am open to other answers that provide more details on the specific problem, such as a JDK bug number for the issue.
As a workaround to make the code compile in JDK 8, an explicit cast can be added to the inner lambda expression:
List<IntToByteFunction> resultList = transform(originalList,
outer -> (IntToByteFunction) inner -> doStuff(inner, outer));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With