Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does type inference fail here?

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.

like image 440
kaqqao Avatar asked Jan 30 '18 00:01

kaqqao


2 Answers

javac doesn't like that DefaultEdge is a raw type.

    return createEdges(nodes, DefaultEdge<N>::new);

will work as expected.

like image 89
Jeffrey Avatar answered Oct 15 '22 01:10

Jeffrey


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());
    }
}
like image 36
Holger Avatar answered Oct 15 '22 02:10

Holger