Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Builder pattern with a Java 8 Stream

I am building an object with a simple loop:

WebTarget target = getClient().target(u);

for (Entry<String, String> queryParam : queryParams.entrySet()) {
    target = target.queryParam(queryParam.getKey(), queryParam.getValue());
}

I want to do the same thing using the Java8 Stream API but I cannot figure out how to do it. What makes me struggle is that target is reassigned every time, so a simple .forEach() will not work. I guess I need to use a .collect() or reduce() since I am looking for a single return value but I am lost at the moment!

like image 623
Francesco Avatar asked Jun 09 '15 15:06

Francesco


People also ask

Which is the correct code to build stream using stream builder () in Java 8?

Stream<String> stream = Stream. of("a", "b", "c"). filter(element -> element. contains("b")); Optional<String> anyElement = stream.

What is the correct code to build stream using stream builder in Java?

Stream builder() returns a builder for a Stream. Syntax : static <T> Stream. Builder<T> builder() where, T is the type of elements.

What is the builder pattern in Java?

The builder pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. The construction is controlled by a director object that only needs to know the type of object it is to create.”

What does Builder () do in Java?

The builder pattern simplifies the creation of objects. It also simplifies the code as your do not have to call a complex constructor or call several setter methods on the created object. The builder pattern can be used to create an immutable class.


2 Answers

It's not very difficult to implement a correct foldLeft for Java 8 streams:

@SuppressWarnings("unchecked")
public static <T, U> U foldLeft(Stream<T> stream, 
                                U identity, BiFunction<U, ? super T, U> accumulator) {
    Object[] result = new Object[] { identity };
    stream.forEachOrdered(t -> result[0] = accumulator.apply((U) result[0], t));
    return (U) result[0];
}

Or in type-safe manner:

public static <T, U> U foldLeft(Stream<T> stream, 
                                U identity, BiFunction<U, ? super T, U> accumulator) {
    class Box {
        U value;
        Box(U value) { this.value = value; }
    }
    Box result = new Box(identity);
    stream.forEachOrdered(t -> result.value = accumulator.apply(result.value, t));
    return result.value;
}

This works correctly for sequential and parallel streams. You can even have a speed gain using parallel streams if your stream has some CPU-consuming stateless intermediate operations like map: in this case the next element can be processed by map in the parallel with the current element processed by foldLeft. I don't agree that such operation is not suitable for Stream API, because it can be correctly expressed via already existing forEachOrdered.

I have this operation in my StreamEx library, so you can use it like this:

WebTarget target = EntryStream.of(queryParams).foldLeft(getClient().target(u), 
        (t, entry) -> t.queryParam(entry.getKey(), entry.getValue()))
like image 156
Tagir Valeev Avatar answered Sep 23 '22 21:09

Tagir Valeev


There's unfortunately no foldLeft method in the stream API. The reason for this is explained by Stuart Marks in this answer:

[...] Finally, Java doesn't provide foldLeft and foldRight operations because they imply a particular ordering of operations that is inherently sequential. This clashes with the design principle stated above of providing APIs that support sequential and parallel operation equally.

Ultimately what you're trying to do here is something procedural / sequential so I don't think the stream API is a good fit for this use case. I think the for-each loop that you have posted yourself is as good as it gets.

Update:

As @TagirValeev points out below you can in fact solve it with the stream API (using forEachOrdered. Your code would then look something like

WebTarget[] arr = { getClient().target(u) };
queryParams.entrySet()
           .stream()
           .forEachOrdered(e -> arr[0] = arr[0].queryParam(e.getKey(),
                                                           e.getValue()));
WebTarget target = arr[0];

I stand by my original answer though, and claim that your good old for-loop is a better approach in this case.

like image 26
9 revs, 2 users 94% Avatar answered Sep 21 '22 21:09

9 revs, 2 users 94%