DEV Community

Brilian Firdaus
Brilian Firdaus

Posted on • Originally published at codecurated.com on

Functional Programming in Java, Explained

Functional Programming in Java, Explained

If you’re a Java developer, I’m sure that you have seen code similar to the featured image snippet above at least once. The code in the snippet above is an example of functional programming paradigm implementation in Java, which will filter and transform the List<String> in the request to another List<String>.

In this article, I will write about how to write code using Java’s API for functional programming. In the end, we will write our own stream API so we can understand how to implement a functional programming style in Java.

Functional Programming in Java

Functional programming in Java has been around for a long time. When Oracle released Java 8 back in 2014, they introduced lambda expression, which was the core feature for functional programming in Java.

Let’s see an example of the difference between using a sequence of imperative statements and using a functional style in Java.

      List<String> stringList = Arrays.asList("Hello", "World", "How", "Are", "You", "Today");

        // imperative declaration
        List<String> filteredList = new ArrayList<>();

        for (String string: stringList) {
            if (string.equals("Hello") || string.equals("Are")) {
                filteredList.add(string);
            }
        }

        List<String> mappedList = new ArrayList<>();
        for (String string: filteredList) {
            mappedList.add(string + " String");
        }

        for (String string: mappedList) {
            System.out.println(string);
        }
Enter fullscreen mode Exit fullscreen mode
Imperative Style

        List<String> stringList = Arrays.asList("Hello", "World", "How", "Are", "You", "Today");

        //functional style
        stringList.stream()
                .filter(s -> s.equals("Hello") || s.equals("Are"))
                .map(s -> s + " String")
                .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode
Functional Style

As we can see, even though both pieces of code achieve the same result, the difference is significant. The imperative declaration code has many curly braces and is much longer, which makes it harder to read, compared to the functional style code.

Functional Interface Annotation

To understand how functional programming works in Java, first we will need to look at the annotation included in Java 8 SDK, @FunctionalInterface. We can look at it on the Java API documentation site.

From the API documentation, we can see that the behaviors of a functional interface annotation in Java are:

  • It has exactly one abstract method in it.
  • It can have more than one method, as long as there is only one abstract method.
  • We can only add it to Interface type.
  • We can create the functional interface with a lambda expression, method references, or constructor references.
  • We don’t need to define @FunctionalInterface because the compiler will treat any interface meeting the definition of a functional interface as a functional interface.

Creating a Functional Interface Class

Now we know what a functional interface all about, we can create it by ourselves.

Let’s first create a model called Person.

package com.example.functional.programming.model;

public class Person {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person(String name) {
        this.name = name;
    }

    public static Person createClassExampleFromMethodReference(String name) {
        return new Person(name);
    }
Enter fullscreen mode Exit fullscreen mode

For the functional interface, we’ll create PersonFunctionalInterfaceclass.


package com.example.functional.programming.intf;

import com.example.functional.programming.model.Person;

@FunctionalInterface
public interface PersonFunctionalInterface {

    Person createPerson(String name);

    default String getDefaultMethodString() {
        return "Default Method";
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that there are two methods in the interface, but since there is only one abstract method, PersonFunctionalInterfaceclass is valid as a functional interface.

But suppose we define more than one abstract method, like so:

package com.example.functional.programming.intf;

import com.example.functional.programming.model.Person;

@FunctionalInterface
public interface PersonFunctionalInterface {

    Person createPerson(String name);

    String mapStringToObject(String str);

    default String getDefaultMethodString() {
        return "Default Method";
    }
}
Enter fullscreen mode Exit fullscreen mode

It will produce an error:

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /D:/Project/functional/src/main/java/com/example/functional/programming/intf/PersonFunctionalInterface.java:[5,1] Unexpected @FunctionalInterface annotation
  com.example.functional.programming.intf.PersonFunctionalInterface is not a functional interface
    multiple non-overriding abstract methods found in interface com.example.functional.programming.intf.PersonFunctionalInterface
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.105 s
[INFO] Finished at: 2020-09-19T10:34:45+07:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project functional-programming: Compilation failure
[ERROR] /D:/Project/functional/src/main/java/com/example/functional/programming/intf/PersonFunctionalInterface.java:[5,1] Unexpected @FunctionalInterface annotation
[ERROR] com.example.functional.programming.intf.PersonFunctionalInterface is not a functional interface
[ERROR] multiple non-overriding abstract methods found in interface com.example.functional.programming.intf.PersonFunctionalInterface
Enter fullscreen mode Exit fullscreen mode

Using a Functional Interface

Anonymous class

Let’s first learn about the anonymous class. Java documentation says that:

“Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.”

Basically, with an anonymous class, we don’t have to define a class that implements the interface we made. We can create a class without a name and store it in a variable.

Let’s declare an anonymous class as an example.


    @Test
    void declareAnonymousClass() {
        PersonFunctionalInterface anonClassExample = new PersonFunctionalInterface() {
            @Override
            public Person createPerson(String name) {
                return new Person(name);
            }
        };

        assert (anonClassExample.createPerson("Hello, World").getName().equals("Hello, World"));
    }
Enter fullscreen mode Exit fullscreen mode

What we’ve done here is we created an anonymous class with PersonFunctionalInterface type and anonClassExample name.

We override the createPerson abstract method so when we call the method, it will return a new Person object with a name.

When we called anonClassExample.createPerson(“Hello, World”), we basically just created a new Person object with “Hello, World” as its name.

Creating an Anonymous Class With a Functional Interface

We can start creating the anonymous class of PersonFunctionalinterface for the functional interface we made.

    @Test
    void interfaceExample() {
        PersonFunctionalInterface normalAnonymousClass = new PersonFunctionalInterface() { // create normal anonymous class
            @Override
            public Person createPerson(String name) {
                return new Person(name);
            }
        };

        PersonFunctionalInterface interfaceExampleLambda = 
                name -> new Person(name); // create anonymous class by lambda
        PersonFunctionalInterface interfaceExampleMethodReference = 
                Person::createClassExampleFromMethodReference; // create anonymous class by method reference
        PersonFunctionalInterface interfaceExampleConstructorReference = 
                Person::new; // create anonymous class by constructor reference

        // assert that every anonymous class behave the same
        assert(normalAnonymousClass
                .createPerson("Hello, World").getName().equals("Hello, World"));
        assert(interfaceExampleLambda
                .createPerson("Hello, World").getName().equals("Hello, World"));
        assert(interfaceExampleMethodReference
                .createPerson("Hello, World").getName().equals("Hello, World"));
        assert(interfaceExampleConstructorReference
                .createPerson("Hello, World").getName().equals("Hello, World"));
        assert(normalAnonymousClass.getDefaultMethodString().equals("Default Method"));
        assert(interfaceExampleLambda.getDefaultMethodString().equals("Default Method"));
        assert(interfaceExampleMethodReference.getDefaultMethodString().equals("Default Method"));
        assert(interfaceExampleConstructorReference.getDefaultMethodString().equals("Default Method"));
    }
Enter fullscreen mode Exit fullscreen mode

We’ve just implemented the functional interface!

In the code above, we created three anonymous classes in different ways. Remember that the anonymous class has the behavior that we can create a functional interface with a lambda expression, method references, or constructor references.

To make sure we created anonymous classes that behave the same, we assert every method in the interface.

Built-In Functional Interface in Java 8

Java 8 has many built-in functional interface classes in the java.util.function package that we can see in its documentation.

In this article, I will only explain four of the most commonly used functional interfaces, but if you’re interested in more, you can read it in the Java API documentation noted above.

  • Consumer<T>: A functional interface that accepts an object and returns nothing.
  • Producer<T>: A functional interface that accepts nothing and returns an object.
  • Predicate<T>: A functional interface that accepts an object and returns a boolean.
  • Function<T, R>: A functional interface that accepts an object and returns another object.

Common Usage

If you’ve been developing with Java a lot, then it’s likely you’ve met the concept of functional interface already.

Stream and optional API

Java’s Stream API uses functional interfaces a lot, as we can see in the code below.

    @Test
    void commonFunctionalInterface() {
        Stream.of("Hello", "World", "How", "Are", "you")
                .filter(s -> s.equals("Hello") || s.equals("Are"))
                .map(s -> s + " String")
                .forEach(System.out::println);

        Optional.of("Hello")
                .filter(s -> s.equals("Hello") || s.equals("Are"))
                .map(s -> s + " String")
                .ifPresent(System.out::println);
    }
Enter fullscreen mode Exit fullscreen mode

The filter method has a parameter Predicate<T> functional interface. As we can see, the method accepts a String and produce a boolean.

The mapmethod uses Function<T, R> as its parameter. It accepts a String and also returns String.

The forEach method in Stream and ifPresent method in Optional accept Consumer<T>, accepting a String and not returning anything.

Reactive library

Both of the most popular Java Reactive libraries, RxJava and Reactor, are based on Java 8 Streams API, which means they also use functional interfaces in their code.

If we look at Reactor’s Flux API documentation and RxJava’s Observable API documentation, we can see many of their methods accept a functional interface.

Creating Our Own Stream API

Now that we know how to create and use a functional interface, let’s try creating our own streaming API so we can understand how we can implement the functional interface.

Of course, our streaming API is much simpler than Java’s.

package com.example.functional.intf;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class SimpleStream<T> {

    private List<T> values;

    public SimpleStream(T... values) {
        this.values = Arrays.asList(values);
    }

    public SimpleStream(List<T> values) {
        this.values = values;
    }

    public SimpleStream<T> filter(Predicate<T> filter) {
        List<T> returnValueList = new ArrayList<>();
        for (T value : values) {
            if (filter.test(value)) {
                returnValueList.add(value);
            }
        }
        this.values = returnValueList;
        return this;
    }

    public SimpleStream<T> map(Function<T, T> function) {
        List<T> returnValueList = new ArrayList<>();
        for (T value : values) {
            returnValueList.add(function.apply(value));
        }
        this.values = returnValueList;
        return this;
    }

    public void forEach(Consumer<T> consumer) {
        for (T value : values) {
            consumer.accept(value);
        }
    }

    public List<T> toList() {
        return this.values;
    }

}
Enter fullscreen mode Exit fullscreen mode

And test class:

    @Test
    void implementingFunctionalInterface() {
        List<String> stringsFromSimpleStream = new SimpleStream<>("Hello", "World", "How", "Are", "you")
                .filter(s -> s.equals("Hello") || s.equals("Are"))
                .map(s -> s + " String")
                .toList();

        assert(stringsFromSimpleStream.size() == 2);
        assert(stringsFromSimpleStream.get(0).equals("Hello String"));
        assert(stringsFromSimpleStream.get(1).equals("Are String"));

        new SimpleStream<>(stringsFromSimpleStream)
                .forEach(System.out::println);
    }
Enter fullscreen mode Exit fullscreen mode

Okay, let’s discuss the methods one by one.

Constructor

We made two constructors, one constructor imitating the Stream.of() API and one constructor to convert List<T> to SimpleStream<T>.

Filter

In this method, we accept Predicate<T> as a parameter since Predicate<T>has an abstract parameter named test that accepts an object and produces a boolean.

Let’s look at the test class, where we wrote:

.filter(s -> s.equals("Hello") || s.equals("Are"))
Enter fullscreen mode Exit fullscreen mode

This means we wrote an anonymous class implementing Predicate<T>:


Predicate<String> filter = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.equals("Hello") || s.equals("Are");
            }
        };
Enter fullscreen mode Exit fullscreen mode

So in the SimpleStream<T> class, we can see the filter method as:


    public SimpleStream<T> filter(Predicate<T> filter) {
        List<T> returnValueList = new ArrayList<>();
        for (T value : values) {
            if (value.equals("Hello") || value.equals("Are")) {
                returnValueList.add(value);
            }
        }
        this.values = returnValueList;
        return this;
    }
Enter fullscreen mode Exit fullscreen mode

Map

In the map method, we accept Function<T, R> as its parameter, which means the map method will accept a functional interface that accepts an object and also produces an object.

We wrote the following in the test class:

.map(s -> s + " String")
Enter fullscreen mode Exit fullscreen mode

It’s the same as creating an anonymous class implementing Function<T, R>:


        Function<String, String> map = new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s + " String";
            }
        };
Enter fullscreen mode Exit fullscreen mode

And in the SimpleStream<T> class, we can see it as this:

    public SimpleStream<T> map(Function<T, T> function) {
        List<T> returnValueList = new ArrayList<>();
        for (T value : values) {
            returnValueList.add(value + " String");
        }
        this.values = returnValueList;
        return this;
    }
Enter fullscreen mode Exit fullscreen mode

forEach

The forEach method accepts Consumer<T> as its parameter, meaning that it will accept an object and return nothing.

We wrote the following in the test class:

.forEach(System.out::println);

Enter fullscreen mode Exit fullscreen mode

This translates to creating an anonymous class implementing Consumer<T>:


Consumer<String> forEach = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
Enter fullscreen mode Exit fullscreen mode

In the SimpleStream<T>, we can see the forEach method, as below:


public void forEach(Consumer<T> consumer) {
        for (T value : values) {
            System.out.println(value);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Conclusion

With the release of Java 8 back in 2014, we can use a functional programming style in Java. Using a functional programming style in Java has many benefits, one of which is making your code shorter and more readable. With the benefits it provides, knowing the implementation of functional programming in Java if you’re a Java developer is a must!

Thanks for reading this article!

You can find the GitHub repository used for this article here:
(https://github.com/brilianfird/java-functional-programming)

Resources

  1. https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
  2. https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
  3. https://www.amitph.com/java-method-and-constructor-reference/#:~:text=Constructor%20Reference%20is%20used%20to,assign%20to%20a%20target%20type.
  4. https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
  5. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
  6. http://reactivex.io/RxJava/javadoc/
  7. https://projectreactor.io/docs/core/release/api/

Oldest comments (0)