msn013
msn013

Reputation: 103

Unable to understand lambdas AND longstream methods

I have such code example.

import java.util.LinkedList;
import java.util.List;
import java.util.stream.LongStream;

public class RemovedNumbers {

    public static List<long[]> removNb(long n) {
        System.out.println(n);
        long sum = ((n + 1) * n / 2);
        long minMultiplication = sum - 2 * n + 1;
        long minCandidate = (long) Math.sqrt(minMultiplication);

        LinkedList<long[]> list = new LinkedList<>();

        LongStream.rangeClosed(minCandidate, n)
                  .mapToObj(a -> new long[]{a, calculateB(a, sum)})
                  .filter(longs -> longs[0] > longs[1])
                  .filter(longs -> longs[1] <= n)
                  .filter(longs -> longs[0] * longs[1] == sum - longs[0] - longs[1])
                  .forEach(longs -> addArrays(list, longs));

        return list;
    }

    private static long calculateB(long a, long sum) {
        return (sum - a) / (a + 1);
    }

    private static void addArrays(final LinkedList<long[]> list, final long[] longs) {
        list.addFirst(new long[]{longs[1], longs[0]});
        list.add(longs);
    }
}

This code is complicated for me in LongStream part. I don't get some points, so I need a help:

  1. I examine LongStream class.
  2. This class uses four methods: rangeClosed, mapToObj, filter, forEach (their description I found on Java docs). Unfortunately, now I am starting to examine java 1.8 version, so I can't understand how it works and what's happens.
  3. Where is appeared "a" in mapToObj? What is it? I don't see var "a" declaration in previous part of code.
  4. As I've got lambda is made by such scheme: (arguments) -> (body). So the "a" is an argument, "new long[]..." - is a body. This part isn't causes any question for me. But the next one, whereis "longs" - argument, "longs[0] > longs[1]" - body, causes some questions. What is the var "longs"? It hasn't declaration in the past! HOW it appears? How it works?
  5. Am I right that LongStream class can be writes in one line? Like: LongStream.rangeClosed().filter().filter().filter().forEach(); ?
    1. Am I right that all methods execute consequently? By each other? The first rangeClosed, then mapToObj, then filter... or is there another order?

Thanks a lot!

Upvotes: 1

Views: 102

Answers (4)

Sweeper
Sweeper

Reputation: 274650

Your third point kind of answers your second point - a is the parameter of the lambda expression passed to mapToObj.

If you can understand that, then your fourth point should be easy to understand as well. longs is the parameter for the lambda expression passed to filter. Remember that you can name your parameter names whatever you like. I guess the reason why the author of the code renamed the parameter to longs is because in the previous line, each long in the stream is mapped into a long[], so now it's a stream of long arrays.

Am I right that LongStream class can be writes in one line?

Yes, but you would end up with a super long line of code, so we almost never do that.

Am I right that all methods execute consequently? By each other? The first rangeClosed, then mapToObj, then filter... or is there another order?

The methods get called in that order, but the operations they do won't run immediately. This is the cool part of streams. The longs will only be mapToObj'ed and filter'ed when you do forEach, a terminal operation. In other words, mapToObj and filter are kind of like saying "this is what this stream should do..." and when you do forEach, you are saying "now do it!"

If you still don't get what streams are doing, try to think of them as a production line in a factory. At the start, you have longs on the conveyer belts. And then they pass through a machine, transforming each of them into a long[]. After that, they pass through three filters. These filters will push them off the conveyer belt unless the long arrays fulfil some condition.

EDIT:

If you want to write this code without lambdas, you can write it with anonymous classes instead:

LongStream.rangeClosed(minCandidate, n)
        .mapToObj(new LongFunction<long[]>() {
            @Override
            public long[] apply(long a) {
                return new long[]{a, calculateB(a, sum)};
            }
        })
        .filter(new Predicate<long[]>() {
            @Override
            public boolean test(long[] longs) {
                return longs[0] > longs[1] && 
                        longs[1] <= n && 
                        longs[0] * longs[1] == sum - longs[0] - longs[1];
            }
        })
        .forEach(new Consumer<long[]>() {
            @Override
            public void accept(long[] longs) {
                addArrays(list, longs);
            }
        });

Upvotes: 2

CodeRonin
CodeRonin

Reputation: 2109

3 and 4:

you are tryng to understand how lambda work so I'll break it down your code for you:

// this return a LongStream obj 
LongStream.rangeClosed(minCandidate, n)
// so with point notation you can access to one of the method in LongStream
// matToObj in this case.
.mapToObj(a -> new long[]{a, calculateB(a, sum)}) 

what is a? What ->? what the other stuff?

MapToObj takes a IntFunction mapper argument and a is a declaration of that type on the fly this is why you didin't see it before in the code. the arrow indicates that the right site is the lamba expression, if you have a inline operation you can omit the return statement and you can not include {} brackets so imagine that statement like a return statement. With lamba functions you can easily create chains of operation this is why you have many functions called one after another. You have to keep in mind that the next function takes as argument an object type that is of the same type of the return type of the previous function.

Upvotes: 0

Ruben Szek&#233;r
Ruben Szek&#233;r

Reputation: 1175

Am I right that LongStream class can be writes in one line?

What you're witnessing here is method chaining. This is where method after method can get chained to eachother. This can be done for almost all classes.

Everything else is pretty much answered by Sweeper.

Upvotes: 0

Eran
Eran

Reputation: 394146

Each lambda expression implements a functional interface, or to be more specific, it implements the single abstract method of that functional interface.

Therefore, in a -> new long[]{a, calculateB(a, sum)}, a is the argument of the method implemented by the functional interface. Since mapToObj accepts an argument of type LongFunction, this lambda expression implements the R apply(long value) method of that interface, which means that lambda expression can also be written as (long a) -> new long[]{a, calculateB(a, sum)}.

This mapToObj call transforms the LongStream to a Stream<long[]>, so the lambda expression of the following filter call - longs -> longs[0] > longs[1] can also be written as (long[] longs) -> longs[0] > longs[1] - it implements the functional interface Predicate<long[]>, which means it implements boolean test(long[] t).

Yes, you can write this entire stream pipeline in a single line, but it would be more readable split into multiple lines.

Am I right that all methods execute consequently? By each other? The first rangeClosed, then mapToObj, then filter... or is there another order

Not exactly. While each intermediate method produces an output used as input to the next method, the evaluation of these methods only begins once the terminal operation - forEach in this case - is executed. And these operations don't necessarily process all the elements of the Stream. For example, if the terminal operation would be firstFirst() instead of forEach, the pipeline would only process enough elements until the first element that passes all the filters is found.

Upvotes: 0

Related Questions