Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda Collect elements created inside an consumer method

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.

like image 647
Johnny Willer Avatar asked Sep 17 '15 13:09

Johnny Willer


2 Answers

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.

like image 119
assylias Avatar answered Nov 15 '22 19:11

assylias


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());
like image 36
a better oliver Avatar answered Nov 15 '22 20:11

a better oliver