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.
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