2

If I have a common sequence of operations in two streams, how can have them be shared to avoid duplicating the code? So far I have found that I can use:

Stream<U> commonSequence(Stream<T> upstream) {
    return upstream.op1().op2().op3()
}

And then use it as

commonSequence(upStream()).downStream()

But then things are not really written in the order in which they happen. Am I overlooking some trick that would allow me to write something like:

upStream().wrap(commonPart).downStream()
xenoid
  • 8,396
  • 3
  • 23
  • 49
  • 3
    Have you checked this answer: https://stackoverflow.com/questions/24474838/can-i-duplicate-a-stream-in-java-8/24474871#24474871 It seems to address a similar (if not the same) question? – Bill Naylor Nov 21 '20 at 22:31
  • 1
    I don't think that's the same: That's performing two different operations on the same stream, while the OP wants to perform the same operations on several streams. – tgdavies Nov 21 '20 at 22:35
  • 1
    Can you be a little biti more specific wrt. the operations? – Turing85 Nov 21 '20 at 22:36
  • @Turing85 No,,, Potentially any sequence of intermediate ops. – xenoid Nov 21 '20 at 22:38
  • @tgdavies Exactly – xenoid Nov 21 '20 at 22:39
  • In this case, there are four options I see: a) either live with the prefix-notation (operator first, then the operand) --- b) Write a `CustomStream extends Stream` that features some kind of wrapper-method --- c) make a (static) helper-method `apply(Stream, UnaryOperator>))` --- d) duplicate the code. – Turing85 Nov 21 '20 at 22:40
  • You could also use an intermediate variable: `List list = new ArrayList<>(); Stream stream = list.stream().distinct(); // upstream commonSequence(stream).collect(Collectors.toList());` – tgdavies Nov 21 '20 at 22:51
  • 1
    But I would just stop worrying about writing things "in the order they happen", we're accustomed to looking at parameter values as something which is calculated before the function they are passed to is applied. – tgdavies Nov 21 '20 at 22:54
  • 2
    Probably not a practical option for you, but Groovy and Kotlin support _extension methods_ for exactly this use case. – chrylis -cautiouslyoptimistic- Nov 21 '20 at 23:48
  • @tgdavies *But I would just stop worrying about writing things "in the order they happen"*: for one common section yes, but if your stream is made of several common sections things get unreadable quite fast. The intermediate variable solution could be the only sensible way in this case. – xenoid Nov 22 '20 at 00:05

1 Answers1

0

You can make your own Collector, but it will likely not be as efficient.

The following collector is somewhat equivalent to doing filter("foo").limit(10).

class FilterFooAndLimit10Collector<T> implements Collector<T, List<T>, Stream<T>> {
  @Override
  public Supplier<List<T>> supplier() {
    return () -> new ArrayList<>();
  }
  
  @Override
  public BiConsumer<List<T>, T> accumulator() {
    return (list, elem) -> {
      if (!"foo".equals(elem)) list.add(elem);
    };
  }
  
  @Override
  public BinaryOperator<List<T>> combiner() {
    return (list1, list2) -> {
      var res = new ArrayList<T>(list1.size() + list2.size());
      res.addAll(list1);
      res.addAll(list2);
      return res;
    };
  }
  
  @Override
  public Function<List<T>, Stream<T>> finisher() {
    return list -> list.stream().limit(10);
  }
  
  @Override
  public Set<Collector.Characteristics> characteristics() {
    return new HashSet<>();
  }
}

You can use it like this:

Stream
  .of("foo", "bar", "baz", "foo", "quux", "corge", "fred", "waldo", "spam", "ham", "eggs", "foo", "foobar", "xyzzy")
  .collect(new FilterFooAndLimit10Collector<String>())
  .forEach(x -> System.out.println(x));

This will yield the output

bar
baz
quux
corge
fred
waldo
spam
ham
eggs
foobar

You can, of course, modify it to take constructor parameters for a wider range of behavior, or optimize it using some other data structure, but I would personally just prefer commonSequence(stream).

user
  • 7,435
  • 3
  • 14
  • 44