Simpler way to start with Reactive Functional Programming than RxJava
Let’s face it, learning functional programming is hard! Especially if you are like me, coming from the traditional object oriented world. In OOP we are used to having a clear hierarchy of objects that mirrors the real world.
To add to the confusion there seems to be functional programming and RFP (Reactive Functional Programming). And terms like “propagation of change”, “side effects” and others don’t help either.
The biggest challenge in learning RFP comes from the lack of a good material and having to learn too many things at once. It requires not only a shift in the mindset but also investing heavily in learning certain RFP APIs. Just look at this excerpt from ReactiveX documentation:
Window
— periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time
RxJava
The most popular RFP library for Java by far is RxJava. It was developed to make your code more concise, handle asynchronous execution, concurrency, back-pressure and etc. RxJava is a powerful beast that can help you design and develop better systems. However, the problem with it is that it can be pretty overwhelming for a beginner. If you’re just trying to get into RFP, RxJava might not be the best place to start.
So what is a good place then? Since RFP is a concept built on top of a functional programming paradigm I think it’s important that you understand functional programming and are comfortable using it first. You need to make a shift in your mindset and wrap your head around the idea without having to learn complex API components and functionalities.
Java Streams API
People developing Java weren’t oblivious to the new trends. They noticed all the hype surrounding RPF and ReactiveX and and came up with their own api — Java 8’s Streams API. Streams API can be used to write complex operations using functional programming style. In my opinion it’s a lot simpler to get proficient at then RxJava. Here’s a diagram that explains Streams API in a nutshell:
- Source Stream — can be created from primitives, objects and collections (Strings, ints, arrays, lists and etc. )
- Intermediate operations — a set of piping methods for manipulating the stream data (sort, filter, map). Each intermediate operation creates a new source stream, so multiple intermediate operations can be chained together.
- Terminal operations — performed to convert the final stream to a value. Only one terminal operation is allowed. (forEach, collect, reduce).
Much simpler isn’t it? Another advantage of Stream API is that it comes with Java 8, so you don’t have to install a separate library to use it.
Even though Streams API seems simpler it doesn’t mean it’s not powerful or flexible enough. It allows you to tune it to achieve a better performance and handle more complex scenarios using concepts like Parallel Streams and etc.
Examples
Now let’s look at some examples of Streams API:
Creating Streams:
Like I mentioned earlier the source stream can created from practically any object, collection or primitive. Streams API has multiple helper methods for creating source streams:
IntStream.range()
:
IntStream.range(1,10);
Stream.of()
:
Stream<String> stream = Stream.of(“one”, “two”, “three”);
List.stream()
:
List<String> list = new ArrayList(); Stream<String> stream = list.stream();List<String> list = new ArrayList();Stream<String> stream = list.stream();
There are many more methods for initiating streams like Stream.builder, Stream.generate, Stream.iterate
, Files.lines
and others that you can find on the internet.
Stream manipulation:
Now let’s look at some simple manipulations you can do with streams that will help you get your hands dirty with functional programming quickly:
- Let start with a simple flow to get a square root of the numbers divisible by 2:
Arrays.stream(new int[] { 1, 2, 39, 405, 204, 482 }).filter(n -> n % 2 == 0).map(n -> n * n).forEach(System.out::println);
2. Let’s create a flow to extract the files from the directory, filter them by the file type and name and output the name of the resulting files:
File.list(Path.get(".").filter(Files::isRegularFile).filter(f -> f.getName().startsWith("bean")).forEach(System.out::println)
3. Lastly, let’s read records from the csv file and get the count of the complete records:
int fullRowCount = Files.lines(Path.get("records.csv")).map(str -> str.split(",")).filter(r -> r.length == 5).count();
As you can see the logic for stream manipulation is pretty straightforward. You will be able to get what’s going on even without having any prior experience with Streams API or functional programming in general.
These examples might seem trivial but they are a good starting point. If you just start using streams to substitute some of your for
and while
loops you’ll get better at it and will start seeing more creative ways of using them in your code.
Conclusion
Functional programming is getting all the hype for a good reason, it really does help you design more elegant systems by abstracting your code and dealing with the smaller implementation details. It provides tools for handling complex scenarios and workflows out of the box which makes your development process that much faster.
Hopefully this article will help you start using functional style programming in your code today!
Originally published at isamatov.com on March 30, 2019.