Passing threads in your example is never better than passing lists:
private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator, List<T>... lists) { ... }
And use it as follows:
Stream<String> result = cartesian( (a, b) -> a + b, Arrays.asList("A", "B"), Arrays.asList("K", "L"), Arrays.asList("X", "Y") );
In both cases, you create an implicit array from varargs and use it as a data source, so laziness is imaginary. Your data is actually stored in arrays.
In most cases, the resulting Cartesian product stream is much longer than the input data, so there is practically no reason for lazy inputs. For example, having five lists of five elements (25 in total), you will get a resulting stream of 3125 elements. Thus, storing 25 items in memory is not a very big problem. In fact, in most practical cases they are already stored in memory.
To generate a stream of Cartesian products, you need to constantly "rewind" all threads (except the first). To rewind, streams must be able to restore the original data again and again, either by buffering them in some way (which you don't like) or by capturing them again from the source (collection, array, file, network, random numbers, etc.). d.). ) and repeat all intermediate operations again and again. If your initial and intermediate operations are slow, then a lazy solution can be much slower than a buffer solution. If your source cannot produce the data again (for example, a random number generator that cannot produce the same numbers that it produced earlier), your decision will be wrong.
Nevertheless, a completely lazy solution is possible. Just use not streams, but streaming providers:
private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator, Supplier<Stream<T>>... streams) { return Arrays.stream(streams) .reduce((s1, s2) -> () -> s1.get().flatMap(t1 -> s2.get().map(t2 -> aggregator.apply(t1, t2)))) .orElse(Stream::empty).get(); }
The solution is interesting, because we create and reduce the flow of suppliers to get the receiving supplier and, finally, call it. Using:
Stream<String> result = cartesian( (a, b) -> a + b, () -> Stream.of("A", "B"), () -> Stream.of("K", "L"), () -> Stream.of("X", "Y") ); result.forEach(System.out::println);