Functional Programming in Java

Reading Time: 7 minutes

What Is Functional Programming

In functional programming, functions are treated a first-class citizens , meaning that they can be bound to names (including local Identifiers), passed as arguments and returned from other functions, just as any other data type can. This allows java programs to be written in a declarative and composable style, where small functions are combined in a modular manner.

Functional programming is sometimes treated as synonymous with purely functional programming. It is a subset of functional programming which treats all functions as deterministic mathematical functions, or pure functions. When a pure function is called with some given arguments then it will always return the same result, and cannot be affected by any mutable state or other side effects. This is in contrast with impure procedures, common in imperative programming, which can have side effects (such as modifying the program’s state or taking input from a user). Proponents of purely functional programming claim that by restricting side effects, programs can have fewer bugs, be easier to debug and test, and be more suited to formal verification.

Basically, functional programming is a style of writing computer programs that treat computations as evaluating mathematical functions. So, what is a function in mathematics?

A function is an expression that relates an input set to an output set.

Importantly, the output of a function depends only on its input. More interestingly, we can compose two or more functions together to get a new function.

Lambda Calculus

To understand why these definitions and properties of mathematical functions are important in programming, we’ll have to go a little back in time. In the 1930s, mathematician Alonzo Chruch developed a formal system to express computations based on function abstraction. This universal model of computation came to be known as the Lambda Calculus.

Lambda calculus had a tremendous impact on developing the theory of programming languages, particularly functional programming languages. Typically, functional programming languages implement lambda calculus.

As lambda calculus focuses on function composition, functional programming languages provide expressive ways to compose software in function composition.

Categorization of Programming Paradigms

Of course, functional programming is not the only programming style in practice. Broadly speaking, programming styles can be categorized into imperative and declarative programming paradigms:

The imperative approach defines a program as a sequence of statements that change the program’s state until it reaches the final state. Procedural programming is a type of imperative programming where we construct programs using procedures or subroutines. One of the popular programming paradigms known as object-oriented programming (OOP) extends procedural programming concepts.

In contrast, the declarative approach expresses the logic of a computation without describing its control flow in terms of a sequence of statements. Simply put, the declarative approach’s focus is to define what the program has to achieve rather than how it should achieve it. Functional programming is a sub-set of the declarative programming languages.

These categories have further sub-categories, and the taxonomy gets quite complex, but we’ll not get into that for this tutorial.

Categorization of Programming Languages

Any attempt to formally categorize the programming languages today is an academic effort in itself! However, we’ll try to understand how programming languages are divided based on their support for functional programming for our purposes.

Pure functional languages, like Haskell, only allow pure functional programs.

Other languages, however, allow both functional and procedural programs and are considered impure functional languages. Many languages fall into this category, including Scala, Kotlin, and Java.

Fundamental Principles and Concepts

This section will cover some of the basic principles of functional programming and how to adopt them in Java. Please note that many features we’ll be using haven’t always been part of Java, and it’s advisable to be on Java 8 or later to exercise functional programming effectively.

First-Class and Higher-Order Functions

A programming language is said to have first-class functions if it treats functions as first-class citizens. Basically, it means that functions are allowed to support all operations typically available to other entities. These include assigning functions to variables, passing them as arguments to other functions, and returning them as values from other functions.

This property makes it possible to define higher-order functions in functional programming. Higher-order functions are capable of receiving function as arguments and returning a function as a result. This further enables several techniques in functional programming like function composition and currying.

Traditionally it was only possible to pass functions in Java using constructs like functional interfaces or anonymous inner classes. Functional interfaces have exactly one abstract method and are also known as Single Abstract Method (SAM) interfaces.

Let’s say we have to provide a custom comparator to Collections.sort method:

Collections.sort(numbers, new Comparator<Integer>() {
    @Override
    public int compare(Integer n1, Integer n2) {
        return n1.compareTo(n2);
    }
});

As we can see, this is a tedious and verbose technique — certainly not something that encourages developers to adopt functional programming. Fortunately, Java 8 brought many new features to ease the process, like lambda expressions, method references, and predefined functional interfaces.

Let’s see how a lambda expression can help us with the same task:

Collections.sort(numbers, (n1, n2) -> n1.compareTo(n2));

Definitely, this is more concise and understandable. However, please note that while this may give us the impression of using functions as first-class citizens in Java, that’s not the case.

Behind the syntactic sugar of lambda expressions, Java still wraps these into functional interfaces. Hence, Java treats a lambda expression as an Object, which is, in fact, the true first-class citizen in Java.

Pure Functions

The definition of pure function emphasizes that a pure function should return a value based only on the arguments and should have no side effects. Now, this can sound quite contrary to all the best practices in Java.

Java, being an object-oriented language, recommends encapsulation as a core programming practice. It encourages hiding an object’s internal state and exposing only necessary methods to access and modify it. Hence, these methods aren’t strictly pure functions.

Of course, encapsulation and other object-oriented principles are only recommendations and not binding in Java. In fact, developers have recently started to realize the value of defining immutable states and methods without side-effects.

Let’s say we want to find the sum of all the numbers we’ve just sorted:

Integer sum(List<Integer> numbers) {
    return numbers.stream().collect(Collectors.summingInt(Integer::intValue));
}

Now, this method depends only on the arguments it receives, hence, it’s deterministic. Moreover, it doesn’t produce any side effects.

Side effects can be anything apart from the intended behavior of the method. For instance, side-effects can be as simple as updating a local or global state or saving to a database before returning a value. Purists also treat logging as a side effect, but we all have our own boundaries to set!.

Immutability

Immutability is one of the core principles of functional programming, and it refers to the property that an entity can’t be modified after being instantiated. Now in a functional programming language, this is supported by design at the language level. But, in Java, we have to make our own decision to create immutable data structures.

Please note that Java itself provides several built-in immutable types, for instance, String. This is primarily for security reasons, as we heavily use String in class loading and as keys in hash-based data structures. There are several other built-in immutable types like primitive wrappers and math types.

But what about the data structures we create in Java? Of course, they are not immutable by default, and we have to make a few changes to achieve immutability. The use of the final keyword is one of them, but it doesn’t stop there:

public class ImmutableData {
    private final String someData;
    private final AnotherImmutableData anotherImmutableData;
    public ImmutableData(final String someData, final AnotherImmutableData anotherImmutableData) {
        this.someData = someData;
        this.anotherImmutableData = anotherImmutableData;
    }
    public String getSomeData() {
        return someData;
    }
    public AnotherImmutableData getAnotherImmutableData() {
        return anotherImmutableData;
    }
}

public class AnotherImmutableData {
    private final Integer someOtherData;
    public AnotherImmutableData(final Integer someData) {
        this.someOtherData = someData;
    }
    public Integer getSomeOtherData() {
        return someOtherData;
    }
}

Note that we have to observe a few rules diligently:

  • All fields of an immutable data structure must be immutable
  • This must apply to all the nested types and collections (including what they contain) as well
  • There should be one or more constructors for initialization as needed
  • There should only be accessor methods, possibly with no side-effects.

Referential Transparency

Referential transparency is perhaps one of the more difficult principles of functional programming to understand. The concept is pretty simple, though. We call an expression referentially transparent if replacing it with its corresponding value has no impact on the program’s behavior.

This enables some powerful techniques in functional programming like higher-order functions and lazy evaluation. To understand this better, let’s take an example:

public class SimpleData {
    private Logger logger = Logger.getGlobal();
    private String data;
    public String getData() {
        logger.log(Level.INFO, "Get data called for SimpleData");
        return data;
    }
    public SimpleData setData(String data) {
        logger.log(Level.INFO, "Set data called for SimpleData");
        this.data = data;
        return this;
    }
}

This is a typical POJO class in Java, but we’re interested in finding if this provides referential transparency. Let’s observe the following statements:

String data = new SimpleData().setData("KRISHNA JAISWAL").getData();
logger.log(Level.INFO, new SimpleData().setData("KRISHNA JAISWAL").getData());
logger.log(Level.INFO, data);
logger.log(Level.INFO, "KRISHNA JAISWAL");

The three calls to logger are semantically equivalent but not referentially transparent. The first call is not referentially transparent as it produces a side-effect. If we replace this call with its value as in the third call, we’ll miss the logs.

The second call is also not referentially transparent as SimpleData is mutable. A call to data.setData anywhere in the program would make it difficult for it to be replaced with its value.

So basically, for referential transparency, we need our functions to be pure and immutable. These are the two preconditions we’ve already discussed earlier. As an interesting outcome of referential transparency, we produce context-free code. In other words, we can execute them in any order and context, which leads to different optimization possibilities.

Why Functional Programming Matters?

After going through the tutorial so far, we must wonder why we even want to take this much effort. For someone coming from a Java background, the shift that functional programming demands are not trivial. So, there should be some really promising advantages for adopting functional programming in Java.

The biggest advantage of adopting functional programming in any language, including Java, is pure functions and immutable states. If we think in retrospect, most of the programming challenges are rooted in the side-effects and mutable state one way or the other. Simply getting rid of them makes our program easier to read, reason about, test, and maintain.

Declarative programming, as such, leads to very concise and readable programs. Functional programming, being a subset of declarative programming, offers several constructs like higher-order functions, function composition, and function chaining. Think of the benefits that Stream API has brought into Java 8 for handling data manipulations.

But don’t get tempted to switch over unless completely ready. Please note that functional programming is not a simple design pattern that we can immediately use and benefit from. Functional programming is more of a change in how we reason about problems and their solutions and how to structure the algorithm.

So, before we start using functional programming, we must train ourselves to think about our programs in terms of functions.

Conclusion

In this tutorial, we went through the basics of functional programming. We covered the fundamental principles and how we can adopt them in Java. 

Referential link :- https://en.wikipedia.org/wiki/Functional_programming .

knoldus

Written by 

KRISHNA JAISWAL is Software Consultant Trainee at Knoldus. He is passionate about JAVA , MYSQL , having knowledge of C , C++ and much more. He is recognised as a good team player, a dedicated and responsible professional, and a technology enthusiast. He is a quick learner & curious to learn new technologies. His hobbies include reading Books , listening Music and playing Cricket .

1 thought on “Functional Programming in Java10 min read

Comments are closed.