Reputation: 103
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:
"a"
in mapToObj? What is it? I don't see var "a"
declaration in previous part of code. (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? LongStream.rangeClosed().filter().filter().filter().forEach(); ?
Thanks a lot!
Upvotes: 1
Views: 102
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
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
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
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