So, I made this relatively simple code, and neither me nor IntelliJ IDEA see anything wrong with it, but javac keels over on the marked line, complaining it can't infer the types:
import java.util.List;
import java.util.stream.Collectors;
public class GenericsBreakJavac8 {
public interface Edge<N> {
N getNode();
}
@FunctionalInterface
public interface EdgeCreator<N, E extends Edge<N>> {
E createEdge(N node);
}
public static <N> List<Edge<N>> createEdges(List<N> nodes) {
return createEdges(nodes, DefaultEdge::new); //the deadly line
}
//THE NEWLY ADDED LINE (see the edit note)
public static <N> List<Edge<N>> createEdges2(List<N> nodes) {
return createEdges(nodes, n -> new DefaultEdge<N>(n));
}
public static <N, E extends Edge<N>> List<E> createEdges(List<N> nodes, EdgeCreator<N, E> edgeCreator) {
return nodes.stream().map(edgeCreator::createEdge).collect(Collectors.toList());
}
public static class DefaultEdge<N> implements Edge<N> {
private final N node;
public DefaultEdge(N node) {
this.node = node;
}
@Override
public N getNode() {
return node;
}
}
}
Splitting the problematic line into 2 with explicit types helps, but the type signature is longer than the lambda, completely defeating the purpose of writing the lambda in the first place...
EDIT: If I use an actual lambda instead of a method reference, I get the problem again. See the newly added method above.
javac
doesn't like that DefaultEdge
is a raw type.
return createEdges(nodes, DefaultEdge<N>::new);
will work as expected.
In addition to Jeffrey’s answer, note that your EdgeCreator
interface doesn’t differ from Function
in its functionality and createEdges
doesn’t actually need the problematic type constraints. So one solution is to let EdgeCreator
extend Function
to simplify createEdges
.
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class GenericsBreakJavac8 {
public interface Edge<N> {
N getNode();
}
@FunctionalInterface
public interface EdgeCreator<N, E extends Edge<N>> extends Function<N,E> {
E createEdge(N node);
@Override public default E apply(N t) { return createEdge(t); }
}
public static <N> List<Edge<N>> createEdges(List<N> nodes) {
return createEdges(nodes, DefaultEdge::new);
}
public static <N> List<Edge<N>> createEdges2(List<N> nodes) {
return createEdges(nodes, n -> new DefaultEdge<>(n));
}
public static <T,R> List<R> createEdges(List<T> nodes, Function<T,R> edgeCreator) {
return nodes.stream().map(edgeCreator).collect(Collectors.toList());
}
public static class DefaultEdge<N> implements Edge<N> {
private final N node;
public DefaultEdge(N node) {
this.node = node;
}
@Override
public N getNode() {
return node;
}
}
}
You could also remove the EdgeCreator
interface and use Function<N, E extends Edge<N>>
at places where the constraint needs to be enforced, though, in this code, there is no such place. The constraint will still get enforced when someone tries to use createEdges(List,Function)
, resp. its result, in a context, where a List<Edge<SpecificNodeType>>
is required.
Carrying the type constraints over too many code locations just inflates your source code for no benefit.
By the way, in your current code, you don’t even need the DefaultEdge
class; you could simplify the entire code to
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class GenericsBreakJavac8 {
public interface Edge<N> {
N getNode();
}
public static <N> List<Edge<N>> createEdges(List<N> nodes) {
return createEdges(nodes, n -> () -> n);
}
public static <T,R> List<R> createEdges(List<T> nodes, Function<T,R> edgeCreator) {
return nodes.stream().map(edgeCreator).collect(Collectors.toList());
}
}
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