2

I want to use Stream API to keep track of a variable while changing it with functions.

My code:

public String encoder(String texteClair) {
    for (Crypteur crypteur : algo) {
        texteClair = crypteur.encoder(texteClair);
    }
    return texteClair;
}

I have a list of classes that have methods and I want to put a variable inside all of them, like done in the code above.

It works perfectly, but I was wondering how it could be done with streams?

Could we use reduce()?

13
  • Is encoder() associative? Commented May 3, 2022 at 4:39
  • 2
    @shmosel not all streams need to be parallel. This is one clear case of a stream that needs to be sequential and ordered; although you're making a good case in principle. Commented May 3, 2022 at 5:18
  • 2
    If I write a sequential stream pipeline, and realize that the implementation arbitrarily parallelized it during execution, I'd report a bug. @shmosel Commented May 3, 2022 at 5:21
  • 1
    @shmosel Nope, I wouldn't say it "encourages", that's pretty close to demanding. But that's reduce. Previous comments generalized that to streams. There are many safe ways of solving for this problem with streams (arguably, reduce() can be one of them in practical terms, although you'd be right to veto that in code reviews - I would too) Commented May 3, 2022 at 6:53
  • 1
    @ernest_k it doesn’t matter which terminal operation you’ll use; if your solution requires the Stream to run sequentially to function correctly, there’s at least one formal rule you’re violating. Bohemian’s answer could be fixed by using forEachOrdered, then it would be formally correct but that doesn’t imply that I would not veto such a solution in code reviews. If you think there is a Stream solution that justifies replacing the straight-forward loop, I’d be happy to see it. Commented May 4, 2022 at 10:16

2 Answers 2

1

Use an AtomicReference, which is effectively final, but its wrapped value may change:

public String encoder(String texteClair) {
    AtomicReference<String> ref = new AtomicReference<>(texteClair);
    algo.stream().forEach(c -> ref.updateAndGet(c::encoder)); // credit Ole V.V
    return ref.get();
}
Sign up to request clarification or add additional context in comments.

4 Comments

This doesn't use streams.
Or for lovers of method references: algo.forEach(c -> ref.updateAndGet(c::encoder));. @shmosel correct, it does even better by using Collection.forEach().
@shmosel it does now :)
A good example why bringing in Streams at all costs is not a good idea. algo.forEach(…) will iterate in the collection’s order if there’s a defined order, whereas algo.stream().forEach(…) is an explicitly unordered operation. The correct call would be algo.stream().forEachOrdered(…) so if the goal was to make the code more complicated for no benefit, it’s working even better.
1

Could we use reduce()?

I guess we could. But keep in mind that it's not the best case to use streams.

Because you've mentioned "classes" in plural, I assume that Crypteur is either an abstract class or an interface. As a general rule you should favor interfaces over abstract classes, so I'll assume the that Crypteur is an interface (if it's not, that's not a big issue) and it has at least one implementation similar to this :

public interface Encoder {
    String encoder(String str);
}

public class Crypteur implements Encoder {
    private UnaryOperator<String> operator;
    
    public Crypteur(UnaryOperator<String> operator) {
        this.operator = operator;
    }
    
    @Override
    public String encoder(String str) {
        return operator.apply(str);
    }
}

Then you can utilize your encoders with stream like this:

public static void main(String[] args) {
    List<Crypteur> algo =
        List.of(new Crypteur(str -> str.replaceAll("\\p{Punct}|\\p{Space}", "")),
                new Crypteur(str -> str.toUpperCase(Locale.ROOT)),
                new Crypteur(str -> str.replace('A', 'W')));
    
    String result = encode(algo, "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system");

    System.out.println(result);
}

public static String encode(Collection<Crypteur> algo, String str) {
    return algo.stream()
        .reduce(str,
            (String result, Crypteur encoder) -> encoder.encoder(result),
            (result1, result2) -> { throw new UnsupportedOperationException(); });
}

Note that combiner, which is used in parallel to combine partial results, deliberately throws an exception to indicate that this task ins't parallelizable. All transformations must be applied sequentially, we can't, for instance, apply some encoders on the given string and then apply the rest of them separately on the given string and merge the two results - it's not possible.

Output

EVERYPIECEOFKNOWLEDGEMUSTHWVEWSINGLEUNWMBIGUOUSWUTHORITWTIVEREPRESENTWTIONWITHINWSYSTEM

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.