Functional Programming in Java beta

Use functional interfaces, lambda expressions and streams to provide operations such as map and filter on a list of items.

The Example

Here we perform functional-style operations on a collection of elements to find the total length of fruit names beginning with 'p'.

SimpleStream.java
package io.lishman.functional.stream;

import java.util.Arrays;
import java.util.List;

public class SimpleStream {

    public static void main(String[] args) {

        List<String> fruits = Arrays.asList("plum", "pear", "apple", "peach", "orange", "pear");

        int length = fruits.stream()
                .filter(fruit -> fruit.startsWith("p"))
                .distinct()
                .mapToInt(String::length)
                .sum();

        System.out.println("Total length of fruit names beginning with 'p' is " + length);
    }
}
Total length of fruit names beginning with 'p' is 13

Let's take a look at some functional concepts and see if we can make some sense of this example.

Functional Interfaces

Functional interfaces have a single abstract method called the functional method. These can be existing interfaces such as Comparator and Runnable or a custom interface that we define.

SingleAbstractMethod.java
package io.lishman.functional.functionalinterface;

import java.util.Comparator;

public class SingleAbstractMethod {

    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return Integer.compare(a, b);
        }
    };

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("running!");
        }
    };

    @FunctionalInterface
    interface MyFunctionalInterface {
        int MyFunctionalMethod(int value);
    }

    MyFunctionalInterface myFunctionalInterface = new MyFunctionalInterface() {
        @Override
        public int MyFunctionalMethod(int value) {
            return value * value;
        }
    };

}

@FunctionalInterface is used to produce compiler level errors when the interface is not a valid functional interface.

Built-in Functional Interfaces

In addition to these specific interfaces, the JDK defines several generic functional interfaces, which you can find in the package java.util.function.

BuiltInExamples.java
package io.lishman.functional.functionalinterface;

import java.util.function.Function;
import java.util.function.IntPredicate;
import java.util.function.LongToDoubleFunction;
import java.util.function.Predicate;

public class BuiltInExamples {

    private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz";

    public static void main(String[] args) {

        //~~~~ Function<T,R>

        Function<String, Integer> function = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        };
        System.out.println("Text size is " + function.apply(ALPHABET));


        //~~~~ [Type]To[Type]Function

        LongToDoubleFunction longToDoubleFunction = new LongToDoubleFunction() {
            @Override
            public double applyAsDouble(long number) {
                return (double) number / 4;
            }
        };
        System.out.println("Quarter: " + longToDoubleFunction.applyAsDouble(55));


        //~~~~ Predicate<T>

        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String value) {
                return value.toUpperCase().equals(value);
            }
        };
        System.out.println("Is upper case? " + predicate.test(ALPHABET));


        //~~~~ [Type]Predicate

        IntPredicate intPredicate = new IntPredicate() {
            @Override
            public boolean test(int i) {
                return i < 0;
            }
        };
        System.out.println("Is negative? " + intPredicate.test(-123));
    }
}

Note that there is nothing magical about these interfaces; nothing Java 8+ specific. In fact Google Guava introduced something very similar long before Java 8.

However, functional interfaces provide target types for lambda expressions and method references - and these are magical!

Lambda Expressions

Let's see how to use lambda expression to implement our functional interfaces rather than using anonymous inner classes.

SimpleLambda.java
package io.lishman.functional.lambda;

public class SimpleLambda {

    @FunctionalInterface
    interface MyFunctionalInterface {
        int MyFunctionalMethod(int value);
    }

    // using an anonymous inner class (to remind us how ugly they are)
    MyFunctionalInterface myFunctionalInterface = new MyFunctionalInterface() {
        @Override
        public int MyFunctionalMethod(int value) {
            return value * value;
        }
    };

    // using a lambda expression
    MyFunctionalInterface myLambda1 = (int value) -> {
        return value * value;
    };

    // we can infer the type
    MyFunctionalInterface myLambda2 = value -> {
        return value * value;
    };

    // and drop 'return' and curly braces for a single statement
    MyFunctionalInterface myLambda3 = value -> value * value;

    // but not for multiple statements
    MyFunctionalInterface myLambda4 = value -> {
        System.out.println("square of " + value);
        return value * value;
    };

}

So we go from this

MyFunctionalInterface myFunctionalInterface = new MyFunctionalInterface() {
    @Override
    public int MyFunctionalMethod(int value) {
        return value * value;
    }
};

to this

MyFunctionalInterface myLambda3 = value -> value * value;

But lambdas really shine when we use them with streams.

Streams

Streams support functional-style operations on a series elements, such as map-reduce transformations on collections.

StreamsWithLambdas.java
package io.lishman.functional.stream;

import java.util.Arrays;
import java.util.List;


public class StreamsWithLambdas {

    public static void main(String[] args) {

        final List<Integer> numbers = Arrays.asList(1, -6, 10, 4, 5, -34, 44, 6, 99, -32, -8, 51);

        final int sum = numbers
                .stream()
                .mapToInt(Math::abs)
                .map(n -> n * 2)
                .filter(n -> n > 10)
                .sum();

        System.out.println("The sum is " + sum);


        final List<String> strings = Arrays.asList("one", "two", "three", "four", "five");

        strings.stream()
                .filter(s -> s.length() > 3)
                .map(String::toUpperCase)
                .forEach(s -> System.out.println(s));

    }
}

The Example Revisited

Let's take a look at our original example again.

SimpleStream.java
package io.lishman.functional.stream;

import java.util.Arrays;
import java.util.List;

public class SimpleStream {

    public static void main(String[] args) {

        List<String> fruits = Arrays.asList("plum", "pear", "apple", "peach", "orange", "pear");

        int length = fruits.stream()
                .filter(fruit -> fruit.startsWith("p"))
                .distinct()
                .mapToInt(String::length)
                .sum();

        System.out.println("Total length of fruit names beginning with 'p' is " + length);
    }
}

The example

  • reads each of the fruit names from the list using the stream method
  • selects only those names beginning with 'p' using filter
  • uses distinct to remove any duplicates
  • finds the length of each name using mapToInt
  • returns the total of all the lengths using sum