Search code examples
javalambdafunctional-interface

Java Functional Interface clarification - Parameters passed into lambda expression


I have the following code I'm struggling to understand. I have one interface declared as below:

public interface WeightedRelationshipConsumer {
    boolean accept(int sourceNodeId, int targetNodeId, long relationId, double weight);
}

Then I have a second interface which accepts WeightedRelationshipConsumer declared as below:

public interface WeightedRelationshipIterator {
    void forEachRelationship(int nodeId, Direction direction, WeightedRelationshipConsumer consumer);
}

Then in an implementation of Dijkstra's algorithm, I have the following code:

private void run(int goal, Direction direction) {
        // `queue` is a Priority Queue that contains the vertices of the graph
        while (!queue.isEmpty()) {
            int node = queue.pop();
            if (node == goal) {
                return;
            }
            // `visited` is a BitSet.
            visited.put(node);
            // Gets the weight of the distance current node from a node-distance map.
            double costs = this.costs.getOrDefault(node, Double.MAX_VALUE);
            graph.forEachRelationship(
                    node,
                    direction, (source, target, relId, weight) -> {
                        updateCosts(source, target, weight + costs);
                        if (!visited.contains(target)) {
                            queue.add(target, 0);
                        }
                        return true;
                    });
        }
    }

It's the

graph.forEachRelationship(node, direction, (source, target, relId, weight) -> {
   updateCosts(source, target, weight + costs);
   if (!visited.contains(target)) {
       queue.add(target, 0);
   }
   return true;
});

that confuses me. Specifically, what are the source, target, relId, weight and how are they resolved? These 4 variables are not defined anywhere else within this class. updateCosts() is as follows:

private void updateCosts(int source, int target, double newCosts) {
    double oldCosts = costs.getOrDefault(target, Double.MAX_VALUE);
    if (newCosts < oldCosts) {
        costs.put(target, newCosts);
        path.put(target, source);
    }
}

Also, if there are resources that may be helpful to understand this type of code, please provide. Thanks.


Solution

  • Your interface appears to be a functional interface:

    interface WeightedRelationshipConsumer {
      boolean accept(int sourceNodeId, int targetNodeId, long relationId, double weight);
    }
    

    These were also called SAM Types (single abstract method types) and they are candidates to be implemented with lambda expressions or method references.

    A lambda expression is a way to implement the only method the interface has.

    For example

    WeightedRelationshipConsumer wrc = (source, target, relId, weight) -> true;
    

    This is a way to provide an implementation for its accept method, where (source, target, relId, weight) correspond to the declared parameters of the method and where true, the return value of the lambda expression, corresponds with the return type of the accept method as well.

    It appears your graph.forEachRelationship method accepts an instance of WeightedRelationshipConsumer as its third parameter, and therefore, you can pass a lambda expression as argument.

    As is the case in you question:

    graph.forEachRelationship(node, direction, (source, target, relId, weight) -> {
       updateCosts(source, target, weight + costs);
       if (!visited.contains(target)) {
           queue.add(target, 0);
       }
       return true;
    });
    

    Regarding the apparent lack of definition for the parameters, it is just a confusion from your part. Lambda expressions support type inference, and so, we're not required to provide the types of the parameters again, they are, after all, already declared in the signature of the method the lambda expression implements (i.e. accept).

    So, our lambda from before could have been alternatively declared as:

    WeightedRelationshipConsumer wrc = (int sourceNodeId, int targetNodeId, long relationId, double weight) -> true
    

    But it is customary to omit the types in order to make them more readable. After all the compiler can infer the types of the arguments from the method signature of accept.

    So, the list of identifiers within lambda parenthesis are in fact parameter declarations for the function.

    There is plenty of reference material here itself, in Stackoverflow, under the java-8 tag