2

I have the following code:

List<DataObject> dataObjectList = new ArrayList<>();
for (Company company : companyRepository.findAll()) {
    for (Employee employee : company.employees) {
        DataOjbect dataObject = new dataObject();
        dataObject.setCompanyName(company.getName());
        dataObject.setEmployeeName(employee.getName());
        dataObjectList.add(dataObject);
    }
}

What is the cleanest way to write this code in a functional style in java?

Note that companyRepository.findAll() returns an Iterator, so you can't simply create a stream out of it.

WJS
  • 36,363
  • 4
  • 24
  • 39
  • 2
    "*Note that `companyRepository.findAll()` returns an Iterator, so you can't simply create a stream out of it.*" [How to convert an iterator to a stream?](https://stackoverflow.com/q/24511052) – VLAZ Aug 27 '20 at 16:45

2 Answers2

2

To create a Stream from Iterator, you need to create Iterable first and then pass its Spliterator using Iterable::spliterator method into StreamSupport::stream.

Stream<?> stream = StreamSupport.stream(iterable.spliterator(), false);

The Iterable can be created from Iterator through a lambda expression as long as Iterable has only one abstract method, therefore the interface is qualified for such expression.

Iterable<Company> iterable = () -> companyRepository.findAll();
Stream<Company> stream = StreamSupport.stream(iterable.spliterator(), false);

Now, the things get easy: Use the advantage of flatMap to flatten the nested list structure (a list of companies where each company has a list of employees. You need to create each DataObject right inside the flatMap method as long as its instantiation relies on the company.getName() parameter:

List<DataObject> dataObjectList = StreamSupport.stream(iterable.spliterator(), false)
        .flatMap(company -> company.getEmployees().stream()
                .map(employee -> {
                    DataObject dataObject = new DataObject();
                    dataObject.setCompanyName(company.getName());
                    dataObject.setEmployeeName(employee.getName());
                    return dataObject;
                }))
        .collect(Collectors.toList());

... and less verbose if you use a constructor ...

List<DataObject> dataObjectList = StreamSupport.stream(iterable.spliterator(), false)
        .flatMap(company -> company.getEmployees().stream()
            .map(employee -> new DataObject(company.getName(), employee.getName())))
        .collect(Collectors.toList());
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
1

Imo, there is no real benefit to using streams to do this. I would stick with what you have. You could make it more concise by creating a constructor in your DataObject class. Then you could do the following:

List<DataObject> dataObjectList = new ArrayList<>();

for (Company company : companyRepository.findAll()) {
    for (Employee employee : company.employees) {
        dataObjectList.add(new DataObject(company.getName(), employee.getName()));
    }
}
WJS
  • 36,363
  • 4
  • 24
  • 39
  • 1
    Even though they ask specifically for a "functional" way, sometimes it's better to KIS and just stick with what works and is readable. +1 – ChiefTwoPencils Aug 27 '20 at 19:34