Functional Programming in Java

Posted on Mar 12, 2023

Functional programming can be achieved in Java, though it needs a lot of discipline to not mix things up. This post discusses the different ways of doing things in a functional way in Java.

When you know the basics of functional programming and want to start with that style in Java, you should keep the following interfaces in your mind:

Supplier       ()    -> x
Consumer       x     -> ()
BiConsumer     x, y  -> ()
Callable       ()    -> x throws ex
Runnable       ()    -> ()
Function       x     -> y
BiFunction     x,y   -> z
Predicate      x     -> boolean
UnaryOperator  x1    -> x2
BinaryOperator x1,x2 -> x3

(List from stackoverflow.com)

These interfaces represent the different types and concepts of functional programming. E.g. the Supplier takes no argument and returns a value. The Function takes one argument and returns one value.

You can see this in the interface description:

@FunctionalInterface
public interface Function<T, R> 

Where T is the input type and R is the output type.

Inheritance

You can start creating functions by implementing this interface. For a Function this could look like the following:

public class ToUppercase implements Function<String, String> {}

A good IDE will tell you then to implement the methods for that. Usually, you’ll just need the apply method in this case (as there are defaults for the other methods).

public class ToUppercase implements Function<String, String> {
    @Override
    public String apply(String s) {
        return s.toUpperCase();
    }
}

Create multiple functions in one class

As an alternative, you can create multiple functions in one class. Using this way you don’t declare the typical function, you’re used to. You create an instance of a Function object instead.

See the following example:

public class StringModifier {
    final Function<String, String> toUppercase = s -> s.toUpperCase();
}

Java also provides functional interfaces for their standard library. You can make use of the syntactic sugar and write the following shorthand:

public class StringModifier {
    final Function<String, String> toUppercase = String::toUpperCase;
}

Let’s add another function into the class:

public class StringModifier {
    final Function<String, String> toUppercase = String::toUpperCase;
    final Function<String, String> removeLastChar = in -> in.substring(0, in.length() - 1);

}

In the following sample you see how to compose functions and evaluate them:

public String apply(String s) {
        return toUppercase
                .andThen(String::trim)
                .andThen(removeLastChar)
                .apply(s);
}

From the functional programming perspective this is the same as the pipe operator!

Composing functions

You can also compose functions and create a new Function:

final Function<String, String> toUppercaseAndRemovedLastChar =
            removeLastChar
                .compose(String::trim)
                .compose(toUppercase);

This is the same as above, but feels not so natural in the way it reads: The order of the functions executed is from right to left. toUppercase -> String::trim -> removeLastChar.

Experiences

When you want to create a pure functional implementation of your application, then you’ll need a lot of discipline.

Otherwise you’ll mix up two different types! OOP and FP can work very well together, but this will lead to a mixed way of thinking during the implementation (see also: imperative vs. declarative).

Functional Programming in a Hexagonal Architecture

When you want to apply this way of programming in a Hexagonal Architecture, you’ll notice at one point that you cannot create the interfaces for the functions you expect in an interface.

So if you want to keep things clean and follow a Hexagonal Architecture, you’ll need to create an abstract class instead of an interface.

Then you can add the functions you expect from outer layers:

public abstract class StringModifier {
    public Function<String, String> toUppercase;
}

But if you just need a Function as an object, you can also define an interface, that extends the FunctionalInterface:

public interface FileWriter extends Consumer<FileWriterOpt> {}

Mixing functional interfaces in a pipe

Another problem that I commonly see is that you cannot chain different interfaces together easily.

Let’s take the following sample:

toUppercase
    .andThen(String::trim)
    .andThen(removeLastChar)
    .apply(s)

It would be great to print the result like this:

toUppercase
    .andThen(String::trim)
    .andThen(removeLastChar)
    .andThen(System.out::println)
    .apply(s);

Unfortunately this won’t work: Javas type system doesn’t allow this.

So, why not create a Consumer<String> and use it like in the following sample?

final Consumer<String> print = System.out::println;

toUppercase
    .andThen(String::trim)
    .andThen(removeLastChar)
    .andThen(print)
    .apply(s);

This won’t work either! The andThen interface of Function requires a Function, not a Consumer!

So to make it work, you need to create a Function<String, Void>:

final Function<String, Void> print = in -> {
    System.out.println(in);
    return null;
};

This doesn’t look so beautiful as it could.

But maybe I just need more experience in the functional programming and design of functions in Java.

Conclusion

Even when Java is not a pure functional programming language, I hardly recommend to try Functional Programming (FP) in Java when you’re working with this language. You’ll better understand how the existing lambda functions like map and reduce work!

Furthermore the existing FunctionalInterfaces provide a good starting point to get into the world of FP. They show what pure functions mean.

But: Also try it out in a pure functional programming language like OCaml or Elixir as this will give you a better understanding of what the declarative way of thinking is!

Resources

dev.java - Learn lambda expression

Jenkov.com - Functional composition