Lambda Collect elements created inside an consumer method - java

I'm changing from ugly nested for loops to a beautiful designed lambda expressions in java.
Here is my actual code
for (String foo : foos) {
for (Bar bar : bars) {
if (bar.getFoo().equals(foo)) {
FooBar fooBar = new FooBar();
fooBar.setBar(bar);
listOfFooBar.add(fooBar);
break;
}
}
}
My actual lambda code to replace code above
foos.forEach(i -> bars.stream().filter(p -> p.getFoo().equals(i)).findFirst().ifPresent(p -> {
FooBar s = new FooBar();
fooBar.setBar(bar);
listOfFooBar.add(fooBar);
}));
My question is, there is a way to populate listOfFooBar with some kind of collect() method?
Something like listOfFooBar = foos.forEach(.....).collect(Collectors.toList());
One fact is that bars will always contain every foo, foos is basically a small part of bars.
If there is a better way (in terms of performance or elegance) to do that lambda, please share.

Since there is only one Bar per Foo, you could start by creating a map linking Foos to Bars:
Map<String, Bar> barsByFoo = bars.stream().collect(toMap(Bar::getFoo, b -> b));
If you have a lot more bars than foos, you can filter:
Map<String, Bar> barsByFoo = bars.stream()
.filter(b -> foos.contains(b.getFoo()))
.collect(toMap(Bar::getFoo, b -> b));
Your nested for loops can then be written:
List<FooBar> listOfFooBar = foos.stream()
.map(barsByFoo::get)
.filter(Objects::nonNull)
.map(FooBar::new)
.collect(toList());
This assumes there is a FooBar(Bar) constructor.
Or you could take the problem from the other side and use an (I think) equivalent algo (you would probably benefit from using a Set<Foo> in that case):
List<FooBar> listOfFooBar = bars.stream()
.filter(bar -> foos.contains(bar.getFoo()))
.map(FooBar::new)
.collect(toList());
Either way, it generally helps to step back from your initial loop as a different algo/approach is generally beneficial to a clean lambda solution.

If you want to go the whole nine yards:
List<FooBar> listOfFooBar = foos.stream()
.flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst()
.map(Stream::of).orElse(Stream.empty()))
.map(bar -> {
FooBar fooBar = new FooBar();
fooBar.setBar(bar);
return fooBar;
})
.collect(Collectors.toList());
If you had a FooBar constructor that accepts a Bar then you could save some lines and write
.map(FooBar::new)
FWIW in Java 9 you will be able to write
.findFirst().stream()
Assuming a suitable constructor it would then shorten to
List<FooBar> listOfFooBar = foos.stream()
.flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).findFirst().stream()))
.map(FooBar::new)
.collect(Collectors.toList());
EDIT:
Using #Misha's suggestion you can shorten it even more:
List<FooBar> listOfFooBar = foos.stream()
.flatMap(foo -> bars.stream().filter(bar-> bar.getFoo().equals(foo)).limit(1)))
.map(FooBar::new)
.collect(Collectors.toList());

If FooBar has a constructor that accepts a Bar as an argument:
public class FooBar {
public FooBar(Bar bar) {
// do something with bar, assign it, etc
}
}
Then, you could do it as follows:
List<FooBar> fooBars = foos.stream()
.map(foo -> bars.stream()
.filter(bar -> bar.getFoo().equals(foo))
.findFirst()
.map(FooBar::new))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
This streams your foos, and for each foo, it streams your bars until it finds the first one that matches the current foo. If a foo is actually found, a new FooBar is created from the inner stream's current bar. This leaves us with a stream of Optional<FooBar>, which is then filtered to only keep the non-empty optionals. Then, the optionals are transformed to the values they contain (which are the FooBars created in the previous step), and finally, these FooBars are collected to a List<FooBar>.
EDIT: That was my first attempt. Much better to use #zeroflagL's approach:
List<FooBar> fooBars = foos.stream()
.flatMap(foo -> bars.stream()
.filter(bar -> bar.getFoo().equals(foo))
.findFirst()
.map(Stream::of).orElse(Stream.empty()))
.map(FooBar::new)
.collect(Collectors.toList());

Related

JAVA8 Optional and Lambdas

Suppose I have this class model hierarchy:
public class A {
private Integer id;
private List<B> b;
}
And:
public class B {
private Integer id;
private List<C> c;
}
And finally:
public class C {
private Integer id;
}
And a simple Service:
#Service
public class doSome {
public void test() {
Optional<A> a = Optional.of(a) // Suppose a is an instance with full hierarchy contains values
/** *1 **/ // what I want to do
}
}
Now what I want to do at the *1 position is to use lambda to extract the Optional value (if exixsts) and map the subrelation to obtain all id of the C class. I have tried something like this:
public void test() {
Optional<A> a = Optional.of(a);
List<Integer> temp = a.get().getB()
.stream()
.map(x -> x.getC())
.flatMap(List::stream)
.map(y -> y.getId())
.collect(Collectors.toList()); // works
}
Now I would like to put inside my lambda the a.get().getB(), I have tried several ways but with no luck.
Anyway I don't understand why I can't use two consecutive map like
.map(x -> x.getC())
.flatMap(List::stream)
.map(y -> y.getId())
without using flatMap(List::stream) in the middle... the map doesn't return a new Stream of Type R (class C in this case)? Why I have to Stream it again? where am I wrong?
----------------------- UPDATE ------------------
This is just an example, It's pretty clear that the Optional here is useless but in real case could comes by a findById() JPA Query.
Holger for this reasons I would put all inside a part of code, doing something like:
public <T> T findSome(Integer id) {
Optional<T> opt = repository.findById(id);
return opt.map(opt -> opt).orElse(null);
}
I have read here some solution like follows:
Optional.ofNullable(MyObject.getPeople())
.map(people -> people
.stream()
.filter(person -> person.getName().equals("test1"))
.findFirst()
.map(person -> person.getId()))
.orElse(null);
And I would like to adapt at my case but without no luck.
As of java-9 and newer, you can call Optional#stream:
List<Integer> temp = a.map(A::getB)
.stream()
.flatMap(Collection::stream)
.map(B::getC)
.flatMap(Collection::stream)
.map(C::getId)
.collect(Collectors.toList());
If you are stuck with java-8, you need to map to Stream (or return the empty one) and continue chaining:
List<Integer> temp = a.map(A::getB)
.map(Collection::stream)
.orElse(Stream.empty())
.map(B::getC)
.flatMap(Collection::stream)
.map(C::getId)
.collect(Collectors.toList());
Note: Optional<A> a = Optional.of(a) is not valid as a is already defined.

Return from function, once value in function is null

I want to exit the current method/function in case a value of an Optional is not present.
i tried a naive approach to put a return statement into the .orElseGet() but that didn't seem to work. The best alternative so far seems to be:
private void foo(Set<ISomeObject> someIterable) {
Optional<ISomeObject> myObjectOpt = someIterable
.stream()
.filter(...)
.findFirst();
if(!myObjectOpt.isPresent()){
return;
}
ISomeObject myObject = myObjectOpt.get();
// do something with myObject
}
This doesn't seem better readable than the good old Nullcheck, any alternatives?
You can pass a Consumer to ifPresent and do whatever you want in the consumer instead:
private void foo(Set<ISomeObject> someIterable) {
someIterable
.stream()
.filter(...)
.findFirst()
.ifPresent(x -> {
// do something with x
});
// do nothing!
}
Well, it seems you want to set the value of ISomeObject myObject in case that Optional is present, one way to do it:
ISomeObject [] myObject = new ISomeObject[1]; // effectively final strikes again!
Optional<ISomeObject> myObjectOpt = someIterable
.stream()
.filter(...)
.findFirst()
.ifPresent(x -> myObject[0] = x);
But even simpler would be:
myObject = someIterable
.stream()
.filter(...)
.findFirst()
.orElse(null); // in your code myObject is null by default anyway
But even this makes very little sense, you are setting that field and then doing nothing with it? Why do that in the first place then?
Also, findFirst from a Set makes little sense, of course.

How to filter data from method call in collector before added to map

I have structure
interface Foo{
List<Bar> getBars();
}
interface Bar{
List<Number> getValues();
}
and list List<Foo> foos
What I want is to create map which will looks more or less like that, with excluding entries where foo does'n have bars or bar does not have values.
{
fooid :{
barId : bar
}
}
obviously my first thought was to do
foos.stream().filter(f->!f.getBars().isEmpty())
.collect( Collectors.toMap(
f->f.id(),
f->f.getBars().stream().filter(b->!b.getValues().isEmpty())
.collect(Collectors.toMap( b->b.id(),b->b.getValues()
))));
problem is i'm calling method getValues twice and it's expensive method,
what will be best practice to handle calls like that?
I think this is what you're looking for:
private static void doMap(List<Foo> foos) {
foos.stream()
.filter(foo -> !foo.getBars()
.isEmpty())
.map(foo -> new SimpleEntry<>(foo.getId(), foo.getBars()
.stream()
.map(bar -> new SimpleEntry<>(bar.getId(), bar.getValues()))
.filter(entry -> !entry.getValue()
.isEmpty())
.collect(entriesToMapCollector())))
.filter(entry -> !entry.getValue().isEmpty())
.collect(entriesToMapCollector());
}
private static <K, V> Collector<Entry<K, V>, ?, Map<K, V>> entriesToMapCollector() {
return Collectors.toMap(Entry::getKey, Entry::getValue);
}
(I would strongly advocate a more imperative approach to this for the sake of readability, maintainability and everything-ability - please don't do this in production code)
Once you realize that what you're doing with a list of Foo and a list of Bar is the same operation, you can solve this problem more easily. This operation is a groupingBy where the id serves as the key and a List serves as the values. You add the condition that a mapping shouldn't be created if the values is an empty list.
This would have been a simple case of grouping the Bars:
bars.stream()
.filter(b -> !b.getValues().isEmpty())
.collect(toMap(b -> b.id(), b -> b.getValues()));
and the Foos:
foos.stream()
.filter(f -> !f.getBars().isEmpty())
.collect(toMap(f -> f.id(), f -> groupBars(f.getBars())));
where groupBars executes the first code block. The problem with grouping the bars this way is that you call getValues twice, which you mention is expensive. The problem with grouping the foos this way is that you filter before knowing if there will be a mapping to an empty value (the result of groupBars).
As shown in Not a JD's answer, a solution to both problems is caching/storing. For Bars, you want to cache the result of getValues. For Foos, you want to cache the result of groupBars so that you can filter on it. The way to cache in a stream is with a container object; in this case Map.Entry works well, but you can use any data storing object. You then map you data into your object (getting a stream of these) and you are able to retrieve its data in different parts of the stream.
The following method groups a list of Bars by their id:
static Map<Integer, List<Number>> groupByIdBar(List<Bar> bars) {
return bars.stream()
.map(b -> Map.entry(b.id(), b.getValues()))
.filter(e -> !e.getValue().isEmpty())
.collect(toMap(Entry::getKey, Entry::getValue));
}
where e.getValue() is the retrieval call from our "cache".
Similarly, this method will group Foos by id:
static Map<Integer, Map<Integer, List<Number>>> groupByIdFoo(List<Foo> foos) {
return foos.stream()
.map(f -> Map.entry(f.id(), groupByIdBar(f.getBars())))
.filter(e -> !e.getValue().isEmpty())
.collect(toMap(Entry::getKey, Entry::getValue));
}
where e.getValue() is the result of groupByIdBar, which allows post-processing (filtering).
You'll notice that the methods are very similar. This is because, as stated in the beginning, we are performing the same operation on them.
The result you want is obtained by groupByIdFoo(foos).
Looping might just be easier in this case:
Map<Integer, Map<Integer, List<Number>>> mapping = new HashMap<>();
for (Foo f : foos) {
List<Bar> bars = f.getBars();
if (!bars.isEmpty()) {
Map<Integer, List<Number>> bMap = new HashMap<>();
for (Bar b : bars) {
if (!b.getValues().isEmpty()) {
bMap.put(b.id(), b.getValues());
}
}
if (!bMap.isEmpty()) {
mapping .put(f.id(), bMap);
}
}
}
If you choose to go this way, you can do some optimization to the code. For example, you can instantiate bMap on the first encounter with a non-empty b.getValues().
For the dataset
Bar b1 = new Bar(1);
b1.values = List.of(10, 20, 30, 40, 50);
Bar b2 = new Bar(2);
b2.values = List.of();
Bar b3 = new Bar(3);
b3.values = List.of(60, 70);
Foo f1 = new Foo(11);
f1.bars = List.of(b1, b2, b3);
Foo f2 = new Foo(22);
f2.bars = List.of();
Foo f3 = new Foo(33);
f3.bars = List.of(b2);
Foo f4 = new Foo(44);
f4.bars = List.of(b1);
List<Foo> foos = List.of(f1, f2, f3, f4);
Both give the result:
{11={1=[10, 20, 30, 40, 50], 3=[60, 70]}, 44={1=[10, 20, 30, 40, 50]}}

How to collect a List<Foo> to a Map<Bar, List<Baz>> using Java lambdas?

I have an object Foo that has references to Bar and Baz objects:
public class Foo {
private Bar bar;
private Baz baz;
public Foo(Bar bar, Baz baz) {
this.bar = bar;
this.baz = baz;
}
}
I have a List<Foo> that I'd like to convert into a Map. I'd like the key to be Bar and the value to be a List<Baz>.
I can create a Map where Bar is the key and the value is a List<Foo>:
Map<Bar, List<Foo>> mapBarToFoos = foos.stream().collect(Collectors.groupingBy(Foo::getBar));
I don't know how to take that last step and turn the value List into a List<Baz>. Is there a lambda conversion on the value that I'm not seeing?
I think you want
list.stream().collect(groupingBy(Foo::getBar,
mapping(Foo::getBaz, toList())));
Where getBaz is the "downstream collector" which transforms the grouped Foos, then yet another which creates the list.
You're close, you'll need to supply a "downstream" collector to further refine your criteria. in this case the groupingBy approach along with a mapping downstream collector is the idiomatic approach i.e.
list.stream().collect(groupingBy(Foo::getBar, mapping(Foo::getBaz, toList())));
This essentially works by applying a mapping downstream collector to the results of the classification (Foo::getBar) function.
basically what we've done is map each Foo object to Baz and puts this into a list.
Just wanted to show another variant here, although not as readable as the groupingBy approach:
foos.stream()
.collect(toMap(Foo::getBar, v -> new ArrayList<>(singletonList(v.getBaz())),
(l, r) -> {l.addAll(r); return l;}));
Foo::getBar is the keyMapper function to extract the map keys.
v -> new ArrayList<>(singletonList(v)) is the valueMapper
function to extract the map values.
(l, r) -> {l.addAll(r); return l;} is the merge function used to
combine two lists that happen to have the same getBar value.
To change the classic grouping to a Map<Bar, List<Foo>> you need to use the method which allows to change the values of the map :
the version with a downstream Collectors.groupingBy(classifier, downstream)
which is a mapping operation Collectors.mapping(mapper, downstream)
which requires another operation to set the container Collectors.toList()
//Use the import to reduce the Collectors. redundancy
//import static java.util.stream.Collectors.*;
Map<Bar, List<Baz>> mapBarToFoos =
foos.stream().collect(groupingBy(Foo::getBar, mapping(Foo::getBaz, toList())));
//without you'll get
Map<Bar, List<Baz>> mapBarToFoos =
foos.stream().collect(Collectors.groupingBy(Foo::getBar, Collectors.mapping(Foo::getBaz, Collectors.toList())));

For loop including if to parallelStream() expression

Is there a way to parallelize this piece of code:
HashMap<String, Car> cars;
List<Car> snapshotCars = new ArrayList<>();
...
for (final Car car : cars.values()) {
if (car.isTimeInTimeline(curTime)) {
car.updateCalculatedPosition(curTime);
snapshotCars.add(car);
}
}
Update: This is what I tried before asking for assistance:
snapshotCars.addAll(cars.values().parallelStream()
.filter(c -> c.isTimeInTimeline(curTime))
.collect(Collectors.toList()));
How could I integrate this line? ->
car.updateCalculatedPosition(curTime);
Well, assuming that updateCalculatedPosition does not affect state outside of the Car object on which it runs, it may be safe enough to use peek for this:
List<Car> snapshotCars = cars.values()
.parallelStream()
.filter(c -> c.isTimeInTimeline(curTime))
.peek(c -> c.updateCalculatedPosition(curTime))
.collect(Collectors.toCollection(ArrayList::new));
I say this is "safe enough" because the collect dictates which elements will be peeked by peek, and these will necessarily be all the items that passed the filter. However, read this answer for the reason why peek should generally be avoided for "significant" operations.
Your peek-free alternative is to first, filter and collect, and then update using the finished collection:
List<Car> snapshotCars = cars.values()
.parallelStream()
.filter(c -> c.isTimeInTimeline(curTime))
.collect(Collectors.toCollection(ArrayList::new));
snapShotCars.parallelStream()
.forEach(c -> c.updateCalculatedPosition(curTime));
This is safer from an API point of view, but less parallel - you only start updating the positions after you have finished filtering and collecting.
If you want parallelized access to a List you might want to use Collections.synchonizedList to get a thread-safe list:
List<Car> snapshotCars = Collections.synchronizedList(new ArrayList<>());
Then you can use the stream API like so:
cars.values()
.parallelStream()
.filter(car -> car.isTimeInTimeline(curTime))
.forEach(car -> {
car.updateCalculatedPosition(curTime);
snapshotCars.add(car);
});
In addition to RealSkeptic’s answer, you can alternatively use your own collector:
List<Car> snapshotCars = cars.values().parallelStream()
.filter(c -> c.isTimeInTimeline(curTime))
.collect(ArrayList::new,
(l,c) -> { c.updateCalculatedPosition(curTime); l.add(c); },
List::addAll);
Note that .collect(Collectors.toList()) is equivalent (though not necessarily identical) to .collect(Collectors.toCollection(ArrayList::new)) which is equivalent to .collect(ArrayList::new, List::add, List::addAll).
So our custom collector does a similar operation, but replaces the accumulator with a function, which also performs the desired additional operation.

Categories

Resources