I seem to have managed to break Java's type inference, with the following code:
public static void main (String[] args)
{
final VirtualFS vfs = new VirtualFS();
vfs.addDirHandler(vf -> {
// top level directory only,
// return a list of all tags
return Arrays.stream("@tag_one @tag_two @tag_three".split(" +"))
.map(str -> (vf.getName() + str))
.map(str -> new VirtualFile(str)) // <-- here
.toList();
});
...
}
The above works fine, but if I change the marked line to:
.map(VirtualFile::new) // <-- here
... then it refuses to compile (although the IDE doesn't complain about it). I can also fix the problem by adding a (String)
cast to the previous .map operation.
The .getName()
method in VirtualFile
returns a String
. Java version is 17, my IDE is Intellij IDEA. Like I said, I can solve the problem with a cast, but I'm curious as to what's going on to confuse the type inference.
The compiler error I get is:
java: incompatible types: invalid constructor reference
incompatible types: java.lang.Object cannot be converted to java.lang.String
Minimum reproducible example:
import java.util.function.Function;
import java.util.*;
public class VirtualFile
{
private final String name;
public VirtualFile(String name)
{
this.name = name;
}
public String getName () { return this.name; }
private static class VirtualFS
{
public void addDirHandler (Function<VirtualFile, List<VirtualFile>> fn)
{
}
}
public static void main (String[] args)
{
final VirtualFS vfs = new VirtualFS();
vfs.addDirHandler(vf -> {
// top level directory only,
// return a list of all tags
return Arrays.stream("@tag_one @tag_two @tag_three".split(" +"))
.map(str -> vf.getName() + str)
.map(VirtualFile::new)
.toList();
});
}
}
This seems like the compiler bug JDK-8268312, where if you call a method taking a Function
like (Stream.map
) in the lambda parameter of another method that takes a Function
(like addDirHandler
), the type information of the former call gets lost.
This is fixed in Java 20.