Go functional with Java
Ajitesh Tiwari
Posted on April 5, 2018
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.
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}
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) -> {}
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); };
}
}
It accepts an object, performs some action and returns no output. So mean :D
Example -
Consumer<String> stringConsumer = string -> System.out.println(string);
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;
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;
}
}
It accepts one object, performs some action on it and returns another object.
Example -
Function<String, String> stringStringFunction = String::toUpperCase;
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);
}
}
It accepts one object, and returns a boolean. Usually defines rules.
Example -
Predicate<String> stringPredicate = String::isEmpty;
Supplier - Create
@FunctionalInterface
public interface Supplier<T> {
T get();
}
It accepts nothing, but returns an object. So generous :D
Example -
Supplier<String> stringSupplier = () -> "Hello, World";
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
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;
}
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
Posted on April 5, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.