DEV Community

Ajitesh Tiwari
Ajitesh Tiwari

Posted on

Go functional with Java

Java has been the most popular language since it's launch, because it's creators have made sure that the language doesn't miss on anything and sustains it originality with the emerging changes.

Functional Programming

Functional programming is a programming concept that is inspired from lambda calculus. In this concept each computation is considered as a function. Although these functions should not be allowed to change state/data outside their scope.
Function

Why?

Software development is an iterative process, which involves not only writing code but also understanding code written by others.

You may find it challenging and vice-versa :D

It can take a lot of time just to figure out the state of a particular object, if it is been changed by functions that use it as an argument. Hence making it difficult to predict the behavior of a program.

Consider it like a third-party API without documentation. You are not sure of what behavior to expect when calling a particular function.

It is not same with concept of functional programming. By not allowing changing state and mutable data it avoids side-effects.

How?

All this theory is fine, but how do I use it as a Java developer?

Most of you must be thinking about this question. The answer is a new member of java family Lambda.

Lambda

Lambda is a function which acts like an object. Can be passed anywhere. Can be executed whenever needed. Can be defined in a single line. Can be created without a class. And much more. It helps to remove a lot of boilerplate code previously required by Java.

Syntax - (Parameter Declaration) -> {Lambda Body}
Enter fullscreen mode Exit fullscreen mode
Examples -
Without parameters - () -> System.out.println("Hello lambda")
With one parameter - s -> System.out.println(s)
With two parameters - (x, y) -> x + y
With multiple line body - (x, y) -> {}
Enter fullscreen mode Exit fullscreen mode

Cool right? but how Java knows which lambda gets mapped to which function?

Digging Deep

Since interfaces are the backbone of lambdas. Java has introduced a new concept known as Functional Interface against which a lambda is defined.

Functional Interface

They are similar to normal interfaces in java with one major difference. They follow SAM rule. According to SAM rule an interface is allowed only single abstract method (SAM).

This check can be enforced at compile time using @FunctionalInterface annotation.

For each lambda we need to create a new functional interface?
That's in-convenient. Right?

Looks like Java has already taken care of this issue as well.
Package java.util.function contains almost 50 functional interfaces. It is highly unlikely that you may need something out of their scope.

Family of functional-interfaces

Mastering all the interfaces may seem like a lot. Rather if we just understand their families, we can point to the right interface easily.

There are 4 families of functional-interfaces -

Consumer - Consume and Discard

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
Enter fullscreen mode Exit fullscreen mode

It accepts an object, performs some action and returns no output. So mean :D

Example -
Consumer<String> stringConsumer = string -> System.out.println(string);
Enter fullscreen mode Exit fullscreen mode

Since lambda function and println() both accept same arguments, this can also be written using Method reference, like this -

Consumer<String> stringConsumer = System.out::println;
Enter fullscreen mode Exit fullscreen mode

Function - Map or Transform or Compute

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
Enter fullscreen mode Exit fullscreen mode

It accepts one object, performs some action on it and returns another object.

Example -
Function<String, String> stringStringFunction = String::toUpperCase;
Enter fullscreen mode Exit fullscreen mode

Predicate - Test or Filter

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
Enter fullscreen mode Exit fullscreen mode

It accepts one object, and returns a boolean. Usually defines rules.

Example -
Predicate<String> stringPredicate = String::isEmpty;
Enter fullscreen mode Exit fullscreen mode

Supplier - Create

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
Enter fullscreen mode Exit fullscreen mode

It accepts nothing, but returns an object. So generous :D

Example -
Supplier<String> stringSupplier = () -> "Hello, World";
Enter fullscreen mode Exit fullscreen mode

Real world

Now it's time to apply our knowledge to some real world challenge. Let's pick up a basic programming challenge from HackerRank - camelCase.

Problem - Count number of words in a camelCase string.

Example - saveChangesInTheEditor
Result - 5
Enter fullscreen mode Exit fullscreen mode

So our motive is not only to solve this problem, but to solve it using the functional way.

The solution is straight forward, count the number of upper-case letter in the string and the result is count + 1.

To do this in functional way we need -

  • Stream of individual characters in the string, and
  • A predicate which helps filter the upper-case characters.
Solution -

/* Stream - s.chars()
Predicate - Character::isUpperCase */

static long camelcase(String s) {
    return s.chars().filter(Character::isUpperCase).count() + 1;
}
Enter fullscreen mode Exit fullscreen mode

Hurrray !

A single line and the problem is solved.

This approach helps developers easily understand the behavior of the snippet. The above line can easily be understood to filter upper-case chars from a string and return value of (count of chars + 1), without any side-effects. Which is what we wanted.

Although becoming functional is not limited to the above example. It is something which is developed with experience, because it is about how we approach a problem.

Java developers may have to put in some extra effort to learn this approach, because our minds are trained to create more code than required. :D

Oldest comments (4)

Collapse
 
elcotu profile image
Daniel Coturel

Great post

Collapse
 
blouzada profile image
Bruno Louzada

Thanks I finnaly understood lambda functions in java! Nice post

Collapse
 
cess11 profile image
PNS11

Nice write-up, thanks.

Collapse
 
thebiggergeek profile image
atanda rasheed ayomide

Now I know the family of lambdas, thanks to you.