Java — Recent Developments

Incorporation of Functional Programming Concepts

Satheesh
Insights from ThoughtClan
4 min readAug 26, 2021

--

Function — An expression which converts a set of inputs into an output
Photo by JESHOOTS.COM on Unsplash

Java has been an important programming language for developers over the past two decades. Still one of the most used languages, it powers countless applications including web and mobile applications, middleware, APIs, media streaming applications etc.

In the past decade, many new languages have been developed, some of which have gained mainstream recognition and usage. Interestingly, some JVM based languages like Kotlin, Scala etc. too have become widely used.

Oracle & Java core development team have been constantly developing the language to ensure that the language provides features required for developing modern applications and maintains its market share in the future.

In this article, we will focus on one of the main themes in how Java has evolved since Java 8 i.e., Functional Programming Concepts being introduced in Java.

One of the first things we learn as Java developers is that Java is an object oriented programming language. Java version 8 made some major changes to introduce basic functional programming concepts to Java; this is one of the major themes in how Java is evolving. While Java still remains statically typed and object oriented (Objects are first class citizens in Java and not functions), many functional style features have been added since version 8 onwards.

Functional Interfaces

Functional interfaces are interfaces which have “exactly one abstract method”. Java 8 introduced many functional interfaces and updated many existing interfaces which fulfils the above criteria as @FunctionalInterface.

Although functional interfaces are not a core functional programming concept, they enable others like function composition & functions which behave like first-class / higher-order functions. Also, developers can define new functional interfaces as part of their project by using @FunctionalInterface annotation.

‘Default’ Methods in Interfaces

Java 8 also introduced the ability to have default non-abstract methods in interfaces. Until then it was not possible to have non-abstract methods in interfaces.

Like the above, this has enabled the possibility of function composition. Default methods like or, and and negate from java.util.function.Predicate interface or compose and andThen from java.util.function.Function interface allow passing on results of one function to the next and so on to perform a flow as a chain of function calls.

Lambda Expressions

Lambda expressions provide a concise way to represent / implement a “single method interface” using an expression. If we have a functional interface and we want to implement it, instead of doing the below,

Comparator<Student> compareByAge = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s1.getAge(), s2.getAge());
}
};

we can simply write the implementation as a lambda expression which is more concise and understandable.

Comparator<Student> compareByAge = (s1, s2) ->
Integer.compare(s1.getAge(), s2.getAge());

Method References

Method References is a syntactical update for calling static / instance methods in a precise manner without having to explicitly write the parameter to be passed while calling. Compiler automatically infers the parameter to be passed to the methods.

Let’s see an example below. It has a lambda expression which prints every element in a list.

var numbers = Arrays.asList(11, 12, 25);
numbers.stream().forEach( item -> System.out.println( item ) );

Using method references, it can be written as below. You will notice that there is no parameter given for println. This is auto-inferred by the compiler based upon what is passed on from each of the forEach iteration.

var numbers = Arrays.asList(11, 12, 25);
numbers.stream().forEach( System.out::println );

Streams

The features described so far in this section like functional interfaces, lambda expressions, method references, default methods culminate in the bigger feature “Streams which enables us to perform functional-style operations on a stream of elements like an array, collection, lines of files, records from database etc.

Let’s see an example below. This loops through a list and prints them one by one.

var numbers = Arrays.asList(11, 12, 25);
for(final Integer item: numbers) {
System.out.println(item);
}

With streams, this can be written in a much more precise way as below.

var numbers = Arrays.asList(11, 12, 25);
numbers.stream().forEach( System.out::println );

We can build much more complex transformations using these features. Below are two examples: the first one takes a list of strings and returns a list of each of their lengths and the second one takes a list of strings, flattens by splitting each of the strings to characters and returns the flattened list.

var numbersAsWords = Arrays.asList( "One", "Two", "Three", "Four" );
var eachStringLength = numbersAsWords.stream()
.map(String::length).toList();
System.out.println(eachStringLength);
var numbersGrouped = Arrays.asList("123", "456", "789", "0");
var numbersSplit = numbersGrouped.stream()
.mapMulti((item, downstream) -> {
Arrays.asList(item.split("")).forEach(downstream::accept);
})
.toList();
System.out.println(numbersSplit);

Streams also support parallel execution. When we use parallelStream() instead of stream() in the above code snippets, the items will be processed in parallel instead of being processed sequentially.

It has to be stressed that while all these features enable “functional style” programming, they are not purely functional. Java treats lambda expressions as objects internally. So, what we have seen in this section provides syntactical ease, developer friendliness and performance; but not pure functional style programming.

While Java has introduced quite a few functional programming concepts, it does not cover other important concepts like pure functions, immutability, tail recursion etc. Considering this and the aforementioned points, one cannot use Java as a pure functional programming language or consider it for any new functional programming development. But these features allow us to write precise and performant code using functional programming constructs when we are upgrading an existing Java application or developing new applications within a Java application landscape.

In the next article in this series “Features to Enhance Developer Friendliness”, I have highlighted some other features introduced in recent versions of Java aimed at making Java more developer friendly.

--

--