📜 ⬆️ ⬇️

We write Java Stream API on the knee in a couple of minutes

Stream API is a great thing that quickly became popular with Java programmers. Laconic one-liners processing data collections through chains of simple operations map, filter, forEach, collect were very convenient. Operations on key-value pairs, of course, would also not hurt, but alas.

In general, it’s pretty clear how this all works, but often the answer to the question “How would I write it?” Helps me a lot to understand the internal mechanisms of this or that technology. It so happened that suddenly for myself I answered this question in relation to the Stream API, the history of the invention of this bike, and I hasten to share with you.

While I calmly wrote IDE components on the swing, the world was changing - javascript was taking over the UI development sphere. And captured. Anyway, high-quality runtime on absolutely every machine is a strong argument. Nothing can be done, I had to understand. In the Java script, the user code is executed in one thread, so all long operations are asynchronous. And if our business logic involves a sequence of such operations, then the first of them must pass the callback, which will launch the second, which pass the callback, which will perform the third, and so on. In general, it reads terribly, painfully supported, js developers somehow try to live with this and come up with workarounds, one of the puzzling options I have encountered is the use of generators. So I found out about them.

For javistes not spoiled by new programming programming, it is worth explaining, the generator is a language construct present in a number of modern languages, in appearance as a function from which a value can be returned many times. Taking such a “function”, an iterator can be assembled from the values ​​emitted by it. Those who were asked to rebuild the iterators at the interview would agree with me - with the help of generators, this becomes a completely trivial task.
')
And so, if a Java developer wanted to make a kind of generator, what would he do? I did this:

public interface Generator<T> { void generate(GeneratorContext<T> context); } public interface GeneratorContext<T> { void emit(T value); } 

The idea is clear, the generate () method deals with generation, a certain context is passed to it by a parameter, and by calling its emit (...) method, you can return multiple values.

Definitely, the data generated by this generator form the entity, let's call it Dataset:

 public class Dataset<T> { private final Generator<T> generator; private Dataset(Generator<T> generator) { this.generator = generator; } } 

And if there is a data set available, then it would be nice to be able to do something with each of their elements. Type there or something. Add the forEach method to the Dataset class:

  public void forEach(Consumer<T> consumer) { generator.generate(value -> consumer.accept(value)); } 

We formed such a generator context that for each call to the emit method, it passes the emitted value to the consumer, and started the generation.

It remains to get somewhere dataset instance and you can experience. Add a factory method that creates a generator from the collection and wraps it in dataset:

  public static <T> Dataset<T> of(Collection<T> collection) { return new Dataset<>(generatorContext -> collection.forEach(item -> generatorContext.emit(item)) ); } 

The same with the good old cycle:

  public static <T> Dataset<T> of(Collection<T> collection) { return new Dataset<>(generatorContext -> { for (T item : collection) { generatorContext.emit(item); } }); } 

Simply ran through the collection and emitted each element. Already you can run:

  Dataset.of(Arrays.asList("foo", "bar")).forEach(System.out::println); 

Conclusion:
foo
bar

And now, as a matter of fact, that most popular task from interviews: we will supplement the data set with elements of another collection. Add a method:

  public Dataset<T> union(Collection<T> collection) { return new Dataset<>(generatorContext -> { generator.generate(generatorContext); collection.forEach(item -> generatorContext.emit(item)); }); } 

We created a new dataset with such a generator, which first emits all the values ​​of the current dataset, and then all the values ​​of the attached collection.

We filter:

  public Dataset<T> filter(Predicate<T> predicate) { return new Dataset<>(generatorContext -> generator.generate(value -> { if (predicate.test(value)) { generatorContext.emit(value); } })); } 

Here we created a new dataset with such a generator, which receives values ​​from the current generator, but then emits only those that have passed the test.

Finally, we transform each element of the data set:

  public <R> Dataset<R> map(Function<T, R> function) { return new Dataset<>(generatorContext -> generator.generate( value -> generatorContext.emit(function.apply(value)) )); } 

Created a new dataset, each element of which will be generated by converting the elements of a given dataset. But the elements of this dataset as such do not exist, they must still be generated. Yes, this is lazy computing.

Now we start everything together.

  Dataset.of(Arrays.asList("", "", "", "")) .union(Arrays.asList("", "", "")) .filter(s -> s.length() > 4) .map(s -> s + ", length=" + s.length()) .forEach(System.out::println); 

Conclusion:
highway, length = 5
sucked length = 6
drying, length = 5

It's time to refactor. The first thing that catches your eye: the GeneratorContext interface can be replaced with a standard Consumer. Replace. In some places, the code will be reduced, as we previously had to wrap the Consumer in the GeneratorContext.

It is worthwhile to stop here and pay attention to a certain similarity between our Dataset and java.util.stream.Stream, which also suggests the relationship of our Generator to the mysterious Spliterators from the java platform.

The bike is ready. I hope you also have a little more understanding of the internal mechanisms of the Stream API, the role of the Spliterator, and the method of organizing lazy calculations.

Ps. Git repo is here .

Source: https://habr.com/ru/post/324228/


All Articles