JavaData & JavaWhat Is Functional Programming in Java?

What Is Functional Programming in Java?

Every programming language is defined by certain syntactical and structural norms. These norms are called programming paradigms. There are different paradigms available: imperative, object-oriented, functional, logic, and so forth. Modern programming languages are complex enough to be built upon one or more paradigms. Functional programming is one of many such paradigms. It emphasizes on declarative aspects of programming where business logic is composed of pure functions, an idea that somewhat contrasts the essence of object-oriented methodology. Intriguingly, Java imbibed this technique within the syntactic structure of the language pretty well. Developers now can weave the functional aspect of their code seamlessly with lambda without breaking the existing architecture or even worrying about backward compatibility. Here, in this article, we’ll detour the fad about functional programming from the perspective of Java.

An Overview of the Programming Paradigm

To put it simply, a programming paradigm is a mode of thinking or abstraction effectively used to model a problem domain. This is what a programming language imbibes into its veins and allows it to be used by the programmer strictly according to that principle. There may be different types of problems that need to be addressed differently varied modes of thinking. This led to the evolution of various programming paradigms and their corresponding programming languages.

The evolution of various programming languages
Figure 1: The evolution of various programming languages

For example, if we look back into the 1940s when machine language was in vogue, a programmer literally had to lay down every instruction in absolute detail. These instructions usually contained states and variables and manipulated memory like an open book. The instructions were minute elaborations of a simple task, such as addition or a logical calculation. Some modern programming languages follow a similar pattern of imperative delineation, maybe not be in entirety, but the principle is there. These types of languages are very good for low-level programming such as building things from scratch or interacting directly with hardware.

Some programming languages follow the principle of declaration rather than delineation. Here, the consistency of representation of what the declaration actually means rests on the interpretation of the declarative norms. This is mostly followed by high-level languages that are mainly focused on getting the job done rather than spending time in the process of describing how it is to be done. A fine example of this type is SQL, where we focus only on, say, fetching the record, rather than describing every detail of how it is to be fetched.

It is, however, intriguing how Java assimilated the functional paradigm into the mainstream of the object-oriented style without any major changes in the design of the language. This not only initiated the use of lambda but also provided the necessary ingredients to a whole new way of expressing a solution statement in Java. However, functional programming is still an optional feature in Java. One can do absolutely everything that one intends to do without using the slightest hint of it, but in doing so, we’ll definitely be deprived of its benefit along the way.

Functional Programming and Lambda Calculus

Functional programming has its origin in the mathematical model called Theory of Functions and lambda in the Lambda Calculus. Lambda expression leverages the functional programming feature. The most visible experience of using lambda in Java is that it simplifies and reduces the amount of source code needed to create certain constructs, such as anonymous classes. But, note that lambda is more than just simplifying the coding construct that meets the eye. In this process, Java introduced a new syntax element—a new operator (the ->)—and the concept of type inference. Many programming languages use this phenomenon in their syntactical structure because it often allows one to write simpler and cleaner code. One of the most common and popular programming languages that leverages the concept of functional programming is JavaScript.

An Overview of OOP vs. Functional Programming

For more than a decade, OOP languages featured almost every need of the programmer and would continue to do so in many years to come, no doubt. However, there are certain situations where functional programming seems to provide a better construct of the solution.

In OOP, everything is represented as an object; therefore, every solution to a problem must be defined as a scheme of classes and their properties even if we need only to implement the behaviour. This type of situation is the niche for functional programming, where we define only the behaviour through functions and not objects. This means in functional programming we directly implement a function rather than an class that contains a function. This is the basic difference between OOP and functional programming.

Realizing Functional Programming with Lambda

Lambda expression is perfect for working out functional programming in Java. This may be explained better with an example. Let’s walk through one to get a better idea.

Suppose we need to implement a simple operation, such as displaying the areas of shapes. One of the ways to do that is as follows.

package org.mano.example;

public class Main {

   public static void main(String[] args) {
      double value = 4.7;
      Main m=new Main();
      Square sqr = new Square(value);
      m.area(sqr);
      Circle circ = new Circle(value);
      m.area(circ);

   public areaOfSquare(double s){
      System.out.println(s*s);
   }

   public areaOfCircle(double rad){
      System.out.println(Math.PI*rad*rad);
   }
}

But, this does not seem to be an elegant design of OOP. What we can do instead is leverage the concept of Polymorphism and rewrite the code.

We create an interface with an abstract method area() and subsequently provide the implementation of this interface as follows.

package org.mano.example;

public interface Shape {
   public void area();
}

package org.mano.example;

public class Square implements Shape{

   private double side;

   public Square(double s) {
      this.side=s;
   }


   @Override
   public void area() {
      System.out.println(side*side);
   }

}

package org.mano.example;

public class Circle implements Shape{

   private double rad;

   public Circle(double r){
      this.rad=r;
   }

   @Override
   public void area() {
      System.out.println(Math.PI*rad*rad);
   }
}

package org.mano.example;

public class Main {

   public static void main(String[] args) {
      double value = 4.7;
      Main m=new Main();
      Shape shp1 = new Square(value);
      m.area(shp1);
      Shape shp2 = new Circle(value);
      m.area(shp2);
   }
   public void area(Shape s){
      s.area();
   }
}

But, the problem here is that we are writing a lot of unnecessary boilerplate code. We can, however, further reduce the code by using anonymous classes as follows.

package org.mano.example;

public interface Shape {
   public void area();
}

package org.mano.example;

public class Main {

   public static void main(String[] args) {
      double value = 4.7;
      Main m=new Main();
      Shape innerSquareArea = new Shape() {
         @Override
         public void area() {
            System.out.println(Math.PI*value*value);
         }
      };

      Shape innerCircleArea = new Shape() {

         @Override
         public void area() {
            System.out.println(value*value);
         }
      };
      m.area(innerSquareArea);
      m.area(innerCircleArea);

   public void area(Shape s){
      s.area();
   }

}

This would have been fine since Java 7. But, with the advent of Java 8, we have a better option of writing code that is simpler, more elegant, and more maintainable. Now, observe what we can do to solve our problem by using simple lambda expression in Java 8.

package org.mano.example;

public class Main {

   public static void main(String[] args) {
      double value = 4.7;
      LambdaType1 circleArea =
         val -> System.out.println(Math.PI*val*val);
      LambdaType1 squareArea =
         val -> System.out.println(val*val);
      circleArea.xyz_nameDoesNotMatter(value);
      squareArea.xyz_nameDoesNotMatter(value);
   }

}

interface LambdaType1{
   void xyz_nameDoesNotMatter(double val);
}

Here, we are literally defining the inline functionality exactly in the place where it is needed. This is the essence of functional programming in Java, where we think in terms of an action rather than an object that contained an action. Let’s add a few more functionalities.

package org.mano.example;

public class Main {

   public static void main(String[] args) {
      double value = 4.7;
      LambdaType1 circleArea =
         val -> System.out.println(Math.PI*val*val);
      LambdaType1 squareArea =
         val -> System.out.println(val*val);

      circleArea.xyz_nameDoesNotMatter(value);
      squareArea.xyz_nameDoesNotMatter(value);

      LambdaType2 rectangleArea =
         (val1, val2) -> System.out.println(val1*val2);
      LambdaType3 toCelcius = f -> (f - 32) * 5 / 9;
      LambdaType3 toFahrenheit = c -> 9 * (c / 5) + 32;

      System.out.println
      (toCelcius.xyz_nameDoesNotMatter(100));
      System.out.println
      (toFahrenheit.xyz_nameDoesNotMatter(37.77777777777778));
   }
}

interface LambdaType1{
   void xyz_nameDoesNotMatter(double val);
}

interface LambdaType2{
   void xyz_nameDoesNotMatter(double val1, double val2 );
}

interface LambdaType3{
   double xyz_nameDoesNotMatter(double val);
}

Observe that each time we add lambda expression that signifies a specific method signature; we also add an interface with a lone function that matches that signature. The name of the method declared within the interface does not matter. This means we can always reuse an interface that matches lambda expression with the method signature declared within it. Another characteristic of a lambda interface is that it must have only one method declaration; otherwise, the compiler cannot infer what method signature to match with the lambda expression type. This gives a clue to understanding how type inference works with lambda expression in Java. Type inference is an important factor associated with lambda. Refer to the type inference Java documentation for more detail. Also, refer to the lambda expression documentation for details on lambda, specifically.

Conclusion

In a way, we can say that programming paradigms are the hypothesis behind their prototype called a programming language. As a general purpose programming language, Java imbibed the functional paradigm where interface and lambda expression played a crucial role in collaborating new rules of functional programming within the DNA of Object-Oriented Java.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories