Skip to content

Functional programming

Introduction

Java is an object-oriented language that relies on creating objects and communicating between them. In this section, we will learn about the functional programming paradigm. Functional programming relies only on functions. The main program is the function we give the arguments and get the result in return. The main function of the program consists only of other functions, which in turn aggregate others.

SAM interfaces

Many interfaces in Java consist of only one abstract method, such as Runnable,Callable, Comparator. Such interfaces are called Single Abstract Method. Java 8 introduces the name FunctionalInterface for this type of component. The Action interface, defined below, is theSAM interface:

public interface Action {
    void execute(int x, int y);
}

The interface below cannot qualify as an interface of type SAM because it has two abstract methods.

public interface Presenter {
    void present(String text);
    void present(String text, int size);
}

@FunctionalInterface

In order to make identification of functional interfaces easier, in Java there was introduced [annotation] (annotations.md) @ FunctionalInterface, which informs the programmer that the indicated interface is supposed to be a functional interface. This annotation should be placed above the interface definition. Attempting to put this information over an interface that has, for example, zero or two abstract methods will result in a compile-time error.

Below we can see the correct use of the @FunctionalInterface annotation:

@FunctionalInterface
public interface Executor {
    void executor(int x);
}

NOTE: The functional interface does NOT need to be marked with the annotation @FunctionalInterface.

default

Java 8 introduces the default method mechanism, specified with the default keyword. A default method is an interface method that has a default implementation (ie, this method has a body). Thanks to this new syntax, within the functional interface, we can declare more than one method, following the principle of a single abstract method, e.g .:

@FunctionalInterface
public interface Executor {
    void executor(int x);

    default void executor(int x, int y) {
      // I have a body I am a default method
    }
}
The interface above is fully compliant with the definition of a component of type SAM. Methods of the default type can be overridden in implementing classes, or they can implement default functionality.

Lambda expressions

The big problem with [anonymous] classes (anonymous_classes.md) is that even if we implement a simple interface with one method, the form of writing can be unreadable. An example of this is passing a function to another method, such as to handle the click of a button.

Thread thread = new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println("Implementation of the Runnable interface as an implementation of an anonymous class!");
  }
});
thread.start();

In Java 8, lambda expressions were introduced, that allow an anonymous class to be treated as a normal function, significantly shortening the syntax of the notation itself.

Thread thread = new Thread(() -> {
  System.out.println("Runnable example using lambda!");
});
thread.start();

As you can see, the lambda expression exempts us from writing the new keyword and using theOverride annotation.

Syntax for the lambda expression

The lambda expression consists of three parts:

  • list of arguments, which:
    • must be in parentheses, if the number of arguments is different from 1
    • may but doesn't have to provide argument types
  • operator ->, which is used after the list of arguments
  • the body of the implemented method, which follows the operator ->
    • if the body consists of one expression then:
      • this body does not have to be inside the braces, i.e. { i }
      • in case this expression returns some object, we can omit the return keyword
    • if the body consists of many expressions, then:
      • this body must be inside the braces, i.e. { i }.

NOTE: if lambda's task is to raise an exception, it has to be done inside the braces, even though it is a single expression.

NOTE: argument names may differ from those defined in the interface.

NOTE: defining a lambda we are completely not interested in the name of the abstract method in the interface.


Let's consider some examples of lambdas.

Example 1

For the following interface:

public interface Action {
    String execute(int x, int y);
}

Lambda expression looks as follows:

  • argument list: (int x, int y)
  • operator ->
  • body of the implemented method: { return x + "-" + y; }

Całość prezentuje się w następujący sposób:

Action action = (int x, int y) -> {
    return x + "-" + y;
};

Note that in the example above, we decided to add optional argument types, declare the body of the lambda inside the braces, and add the word return. We can omit all these elements and reduce the notation to a single line:

Action action = (x, y) -> x + "-" + y;

Example 2

Let's consider the Runnable interface:

@FunctionalInterface
public interface Runnable {
  public abstract void run();
}

An example implementation could look like this:

Runnable runnableExample = () -> {
  System.out.println("Hello from runnable");
  System.out.println("{ and } cannot be omitted");
};

Note that in the absence of arguments, we must use (). The body of the lambda above consists of two statements, so it must be defined inside { }.

Example 3

Another example would be to use the FruitEater functional interface with the following definition:

@FunctionalInterface
public interface FruitEater<T> {
    void consume(T t);
}

The following implementation has one input argument, so we can omit the parentheses by giving the argument list:

FruitEater<String> fruitEater = fruit -> System.out.println(String.format("eating %s... omnomnom", fruit);

Method references

Some simple lambda expressions we can write with method reference. Instead of writing a method call, we can only give its name. In this case, the name of the class and method must be separated by the characters '::'.

We can use a method reference when the lambda has one argument and one of the following conditions is met:

  • lambda input argument is an argument of a method from some class
  • an argumentless method is called on the input argument.

The following examples show possible uses of a method reference:

Consumer<String > consumerExample = someString -> System.out.println(someString); // use of lambda
Consumer<String > consumerExampleReference = System.out::println; // identical notation as in the line above, use of references
List.of("someString").stream().map(str -> str.toUpperCase()); // using lambda in the map method
List.of("someString").stream().map(String::toUpperCase); // using a reference to a method, equivalent notation in the line of code above

Functional interfaces in Java 8

Java 8 introduces many useful functional interfaces that can be used, among others in [Stream API] (stream_api.md). All the interfaces that will be discussed are in the java.util.function package. The most commonly used are:

  • Supplier<T>
  • Function<T, R>
  • Consumer<T>
  • UnaryOperator<T, T>
  • Predicate<T>

Function

Function is a generic function interface that takes an object of any type (T) and returns an object of any type (R). The apply method is responsible for calling the implemented action. The following example shows the use of this interface:

public class FunctionExample {
  public static void main(String[] args) {
    Function<Employee, String> employeeToString = (employee) -> employee.getName(); // (1)

    List<Employee> employees = Arrays.asList(new Employee("test"), new Employee("test2"));
    showEmployee(employees, employeeToString);
  }

  static void showEmployee(List<Employee> employees, Function<Employee, String> showFunction) {
    for (Employee employee : employees) {
      System.out.println(showFunction.apply(employee));;
    }
  }
}

class Employee {
    private String name;

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

    public String getName(){
        return name;
    }
}

In the above example, in line (1), we are creating an implementation of the Function interface using a unary lambda that returns the value of thename field in the Employee object. Since this lambda consists of one expression, we don't have to write the return keyword.

Supplier

The Supplier is a generic functional interface which responsibility is to provide the value of an object of typeT. The get method returns a value implemented in the interface.

The following example uses a lambda expression to implement the Supplier interface:

public class SupplierExample {
  public static void main(String[] args) {
    getValue(() -> "supplier test!");
  }

  static void getValue(Supplier<String> supplier){
    System.out.println(supplier.get());
  }
}

Consumer

Consumer is another generic functional interface. Represents an operation that accepts a single input argument and returns no result. The generic type T is an argument to the method. The interface is used when there is a need to "consume" the object. The accept method is responsible for calling the implemented method, e.g .:

public class ConsumerExample {
  public static void main(String[] args) {
    Consumer<String> stringTrim = (s) -> {
      s = s.trim();
      System.out.println(s);
    }; // Consumer implementation using multi-line lambda

    trimValue(stringTrim, "   text    ");
  }

  static void trimValue(Consumer<String> trimAction, String s) {
    trimAction.accept(s);
  }
}

Predicate

The function interfaces Predicate represents an operation that accepts a single argument and returns a logical value based on the parameter passed. So it is a specialization of the Function interface. The generic type T is the type of the method's input argument.

The test method is responsible for returning the logical value of the test, e.g.

public class PredicateExample {
  public static void main(String[] args) {
    Predicate<Integer> predicate = (value) -> {
      return value >= 0;
    };
    checkTest(predicate);
  }

  static void checkTest(Predicate<Integer> predicate) {
    System.out.println(predicate.test(-1));
  }
}

Optional

In Java, an object reference can either point to a real object or it can be empty, that is, null. From a programmer's perspective, it is inconvenient to validate the reference each time. Therefore, we often carry out such validation, e.g. when:

  • the documentation tells us about possible null values
  • relevant parts of the method are marked with annotation, e.g. @Null or@Nullable

Instead, we can use the mechanism introduced in Java 8. The Optional in the packagejava.util is an object that wraps the target, ie, it is some kind of box that such an object may or may not contain.

The Optional class gives us access to many methods, including:

  • of
  • ofNullable
  • isPresent
  • ifPresent
  • orElse
  • orElseGet

Create an object of type Optional

The static method of orofNullable is responsible for creating objects. The difference between the first and the second method is, the fact that the first method does not allow taking a value of type null.

The example shows both ways of creating described above:

public class CreatingOptionals {
  public static void main(String[] args) {
    final Optional<String> stringOptional = Optional.of("This is SDA!"); // creating a box with an object of the String type

    String value = null;
    if ((Integer.parseInt(args[0]) % 2 == 0)) {
      value = "I am even";
    }
    final Optional<String> optionalThatCanBeEmpty = Optional.ofNullable(value); // use ofNullable since value can be null. In this case, Optional will be an empty box
  }
}

Checking values

The Optional class provides methods that allow you to perform an operation conditionally, if it actually contains a reference to an object.

  • the isPresent method returnstrue / false depending on whether theOptional holds any object
  • the isEmpty method returns a value opposite to the value returned by theisPresent method, i.e. it gives information whether Optional does not store an object
  • the ifPresent method takes theConsumer function interface as an argument. This consumer will only be called when Optional holds a reference to an object.

The following example shows the use of these methods:

public class OptionalsPresenceExample {
  public static void main(String[] args) {
    final Optional<String> optional = getStringForEvenNumber(3);
    if (optional.isPresent()) {
      System.out.println("I am optional with a value, I am non empty box");
    } else if (optional.isEmpty()) { // warunek zawsze prawdziwy w tym momencie
      System.out.println("I am an empty optional");
    }

    optional.ifPresent(System.out::println); // writing the values to a box on the screen, only if available
  }

  private static Optional<String> getStringForEvenNumber(final int number) {
    if (number % 2 == 0) {
      return Optional.of("even");
    }
    return Optional.empty();
  }
}

Getting a value

The Optional class provides several methods for getting a value:

  • the get method returns the value stored in the object, or in the case of null it throws an exception:NoSuchElementException
  • the orElse method returns the value stored in Optional or the value indicated as the argument of the method in the case of an empty Optional
  • the orElseGet method returns the value stored in Optional or the value indicated by Supplier, which is an input argument.
public class OptionalOrElseExample {
  public static void main(String[] args) {
    String object = null;
    String name = Optional.ofNullable(object).orElse("john");
    System.out.println(name); // the value john will be printed on the screen
  }
}
public class OptionalOrElseGetExample {
  public static void main(String[] args) {
    String object = null;
    String name = Optional.ofNullable(object).orElseGet(() -> "john");
    System.out.println(name); // String john reappears on the screen
  }
}

NOTE: If we want to use the get method, we should first check the availability of an object using theisPresent method.

NOTE: The method orElseGet will only compute the replacement value as opposed to orElse if Optional is empty. This means that the performance of the orElseGet method is better than that of the orElse method.