Introduction

Functional programming is a programming paradigm that promotes the use of functions and the avoidance of changing state and mutable data. This style of programming can lead to more concise, expressive, and maintainable code. The best way to start making use of it is by using the Java Streams API.

Java, being one of the most widely used programming languages, has also adopted functional programming concepts and features. In this post, we will take a look at how functional programming can be applied in Java 8 using the Streams API and functional interfaces.

Functional programming has several benefits over imperative programming, such as:

  • It promotes immutability, which means that data cannot be modified once it has been created. This can lead to fewer bugs and a more predictable program.
  • It encourages the use of pure functions, which are functions that always return the same output for the same input, and do not have any side effects. This can make code more testable and reusable.
  • It allows for the creation of higher-order functions, which are functions that take other functions as input or return functions as output. This can lead to more expressive and reusable code.

The Streams API

The Streams API is a powerful and flexible API that allows you to perform operations on collections of data in a functional way. It provides a fluent API for working with collections of data, such as filtering, mapping, and reducing.

Java Streams 101

First things first, if you are reading this article, chances are this is one of the first times you’re hearing about functional programming. If that’s your case, let me introduce you to one easy concept you must understand in order to master the use of the Streams API.

I’m talking about terminal vs intermediate operations. You have to think of Streams as a pipeline, each operation performed by a Stream either terminates or not the Stream.

Some examples of non-terminal (or intermediate) operations are:

  • map -> Transforms the Stream from type A to type B.
  • filter -> Filters the elements in the Stream.
  • limit -> Limits the elements in the Stream.

All those functions do not terminate the stream and as a result, they return another stream. That is to say, they perform some operation for the given stream and return a different stream.

Some examples of terminal operations are:

  • collect -> Collects the elements in the stream (usually to a List)
  • forEach -> Perform an operation for each element in the stream

Those functions terminate the stream and thus, they require a semicolon as a regular line of code ender in Java.

Read more about it here

Now, let’s talk next about the most basic -yet most used- Stream’s functions.

Map

I would probably say it’s the most useful and the first Stream function any programmer should learn.

Have you ever experienced having a list of objects, let’s say of class Animal, and wanted to iterate through the list just to collect some property (such as name, age, color…)?

Most of the Java developers would probably create a new ArrayList for storing this property, iterate the list, probably using a for-loop and collect the given property in the new list. Something like this:

Was I close to what you were expecting? I guess so.

Quick disclaimer! There’s nothing wrong with this approach, I’ll write a post regarding whether you should use streams over loops. For the sake of this post, let’s just say there are multiple available approaches up to you.

If you wanna read more about this, feel free to look for some information yourself. This can be your starting point though.

Now, this could be easily done as well with Java streams like so:

Lambda expression

In this example, we are leveraging the existence of the map function. As a non-terminal operation that takes a lambda expression as the argument, it transforms the given Stream, in this case from type Animal to type String. In this case, we’re specifying that for each animal in the animals list, it should be mapped to a string, by using its property name and then collecting everything to a list that is returned.

You can also use the method reference of the Animal class

Method reference

Notice as well that there’s no need for curly braces when the lambda expression is a one-liner.

Filter

Another function you should be familiar with. It expects a Predicate (don’t worry about it, it’s basically a lambda expression/method reference) as an argument to filter the given stream. Since it’s a non-terminal operation, it returns another stream. This is one of the main benefits of using the Streams API, being able to chain call.

Easiest example ever, given a list of integers, return those that are greater than 10:

ForEach

This one is a terminal operation and as so, it ends the stream. It expects a Consumer which as a function that will be performed to each of the elements in the Stream.

Let’s say that for each filtered number of the previous example, we want to output it to the console, instead of adding it to a list:

Notice how I used the method reference so that I don’t have to write a lambda expression.

Some more examples

Let’s say we have a list of integers and we want to find the sum of all even numbers in the list. With the Streams API, we can do this in a single line of code:

Given a list of integers, return the sum of all even numbers

In this example, we first create a stream of the list of numbers, then use the filter method only to keep even numbers, then we use the mapToInt method to convert the stream of Integers to a stream of primitive ints, and finally, we use the sum method to find the sum of all numbers in the stream.

Functional interfaces are another functional programming feature introduced in Java 8. They are interfaces that have a single abstract method, such as Predicate, Function, and Consumer. These interfaces can be used to create lambda expressions and method references that can be passed as arguments to methods.

For example, let’s say we have a list of strings and we want to print all strings that are longer than 3 characters. With functional interfaces, we can do this in a single line of code:

Given a list of strings, print out those longer than 3 characters in a one-liner

In this example, we first create a list of strings, then we use the forEach method and pass a lambda expression that checks if the length of a string is greater than 3, and if so, it prints the string.

Conclusion

Functional programming is a powerful and expressive way to write code, and with the introduction of the Streams API and functional interfaces in Java 8, it’s now easier than ever to write functional code in Java. While it’s not always the best solution for every problem, understanding the concepts of functional programming is an extremely important tool for every Java developer.

At first, you might want to give it a try and practice this new way of thinking. Once you get used to it, you will realize the high potential of using functional programming.