Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map a single object using Stream

Is this a good use case of java 8's Stream:

final Car carWithInterior = Stream.of(carWithEngine)
            .map(car -> installSeats.execute(car))
            .map(car -> installDashBoard.execute(car))
            .map(car -> installSunRoof.execute(car))
            .collect(car);

carWithEngine is a Car. Note that sometimes even if the car (w/ dashboard) is pass to the installSunroof, it will not do any since there's no hole in the roof. I should always get a car at the end of the installation/mapping process.

The installation sequence is required (that's why I thought of streaming it) and next installation may sometimes need a parameter from the pass car instance to perform it's operation.

  1. Is this a good use case for java 8's Stream?

  2. Obvioulsy, my collect at the end is not right. How should I get the car at the end of this installation/assembly line? findFirst().get() will work but I think that's bad, since I should always get a car at the end even if the installation didn't do anything with the carWitEngine and I'm not stream multiple elements.

I'm not sure how cars are assembled but let say you need to put the engine first before adding the interiors for the sake of this analogy

like image 645
letthefireflieslive Avatar asked Jan 02 '23 23:01

letthefireflieslive


2 Answers

Since you're looking to perform operations on a single object. Java 8 Optional would be appropriate.

The builder pattern (Phil C's comment) is definitely worth visiting if each of these map operations are simply hydrating the Car object.

  1. Tapping open a stream for a single object is not a good use case for streams.
  2. You're venturing into dangerous territory (side-effecty) if the Car gets dropped in the stream through a .map() operation. When dealing with Streams it is highly recommended to use these operations the way they are intended. If you plan on dropping an element from a stream .filter()

Edit to OP's edits:

There is some fundamental confusion around "sequence" and how that that translates

Optional

 car => map(execute) => map(execute) => transformedcar

Streams

[car1, car2, car3]
     => map(execute) => map(execute) => updatedcar1                |  => findFirst (Optional<Car>)
           => map(execute) => map(execute) =>  updatedcar2         |
                 => map(execute) => map(execute) => updatedcar3    |
                                                                collect
                                                 [updatedcar1, updatedcar2, updatedcar3]

The findFirst method will return an Optional. The collect method will provide a terminal operation that lets you aggregate/reduce/group these results.

These map operations transform the passed elements "sequentially". But a Stream would be appropriate cases where you must deal with many or "sequence of" cars.

To revisit #2

You have the option of providing an alternative with Optional with orElse(car).

But this is where you have to be contentious of the side effects you are performing.

If the execute method is manipulating and returning the same object that was passed into it, there will be unintended consequences. I'll be using Optionals for example but same will apply for Streams.

private final Car car Optional.of(carWithEngine)
   .map(installSunroof.execute)   // <-- updatedCarWithSunroof
   .map(installDashboard.execute) // <-- updatedCarWithSunroofAndDashboard
   .orElse(carWithEngine)         // <-- updatedCarWithSunroofAndDashboard
like image 161
shinjw Avatar answered Jan 05 '23 07:01

shinjw


Answering your questions:

  1. Is this a good use case for java 8's Stream?

No, the Java Stream Api is meant to be use on collections. From here the definition of a stream is:

a sequence of elements from a source that supports aggregate operations

You have a single object that is not a sequence of elements, so the use of Stream make your code more difficult to understand. To answer question 2, you can do

findFirst().orElse(carWithEngine)

An alternative to your example is simply to nest the function calls, like this:

installSunRoof.execute(installDashBoard.execute(installSeats.execute(car)))

Another alternative will be to define a method install on the car that receives a BaseIinstaller and returns the modified car, something like this:

final Car carWithInterior = car.install(installSeats)
                               .install(installDashBoard)
                               .install(installSunRoof);
like image 21
Dani Mesejo Avatar answered Jan 05 '23 07:01

Dani Mesejo