Basically, you can replace nested loops with Stream using flatMap . This requires that you select an item type that can store information equivalent to loop variables if you need it. In your case, these are two values ββfor x and y . This would simplify the code if your Node class stores this information, since you can easily iterate over nodes, not int values. Since you did not specify the capabilities of your Node class, here is an example that uses a long[] size two to store points:
IntStream.range(0, width).boxed() .flatMap(x->IntStream.range(0, height).mapToObj(y->new int[]{ x, y })) .forEach(p1 -> { Consumer<Node> register=getNode(p1[0], p1[1])::registerObserverAtNeighbor; IntStream.rangeClosed(p1[0]-1, p1[0]+1).boxed() .flatMap(x->IntStream.rangeClosed(p1[1]-1, p1[1]+1).mapToObj(y->new int[]{ x,y })) .filter(p2 -> (p1[0] != p2[0] || p1[1] != p2[1])) .map(point -> getNode(point[0], point[1])) .filter(node -> node != null) .forEach(register); });
It still simplifies the innermost code by moving the code outside where possible, for example. getNode calls. You can also simplify the code by setting the repetitive task of creating Stream over points for an area in a method:
static Stream<int[]> area(int x0, int x1, int y0, int y1) { return IntStream.range(x0, x1).boxed() .flatMap(x->IntStream.range(y0, y1).mapToObj(y->new int[]{ x, y })); }
Then you can use it as follows:
area(0, width, 0, height).forEach(p1 -> { Consumer<Node> register=getNode(p1[0], p1[1])::registerObserverAtNeighbor; area(p1[0]-1, p1[0]+2, p1[1]-1, p1[1]+2) .filter(p2 -> (p1[0] != p2[0] || p1[1] != p2[1])) .map(point -> getNode(point[0], point[1])) .filter(node -> node != null) .forEach(register); });
It may still be easier if you use / use a dedicated point class or the node class contains point information (and at best has a comparison method for it).