2

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.

1

1 Answer 1

3

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. How about the actual definitions of source, target, relId and weight? updateCosts() need them, however, I can't see them being defined anywhere.
@swdon I just extended my answer to address your question.
thanks, I have one last clarification. Regarding the forEachRelationship() which is an abstract method, what actually happens when the arguments are passed to it? I don't see a function body for it anywhere. I think that's where my confusion comes from. Please help clarify this, I will accept your answer. Thanks so much.
@swdon You have not provided details of the declaration of the forEachRelationship method, its class and its derivates, so how in the world could I know the answer to your question? What I can infer from it, though, is that even when the reference graph is from some abstract type, it evidently points to an implementation of that abstraction where the forEachRelationship method is indeed defined. If you get the value of graph.getClass(), then you`ll probably find what is this derivate class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.