Functional Programming in Java

As I stated in my previous post I'm deep diving into Java because that's the primary language used at Amazon. The first thing I did was investigate what functional aspects have been added to the language since I last used it, because I enjoy the functional nature of JavaScript. In this tutorial, I'll show you what I've learned so far.

Our Previous Example


To begin, let's rewrite the Java example used in the previous post in order to show how Java has evolved to handle functions with Java 8. Originally we had:

public static Integer addOne(Integer number){
    return number + 1;
} 

public static Integer squareNumber(Integer number){
    return number * number;
} 

public static Integer composeFunctions(Integer input){
    return addOne(squareNumber(input));
}

This can be rewritten functionally as:

public static Function<Integer,Integer> addOne = (Integer number) -> {
    return number + 1;
};

public static Function<Integer,Integer> squareNumber = (Integer number) -> {
    return number*number;
};

public static Integer composeFunctions(Function f1, Function f2, Integer input){
    return (Integer)f2.apply(f1.apply(input));
}

You'll notice a few peculiarities that don't match up with the JavaScript example. The first is the lambda functions. In the case of our JavaScript example, we were able to assign our functions to variables:

var fun = function(){
    console.log("I'm a function!");
}

(anyone? anyone? alright...sorry back to the code....)

However, we can use a slick bit of syntax named the "fat-arrow function" to write this another way:

var fun =  () => {
    console.log("I'm a Star Wars");
}

That's all this is in the Java code, but they're called "lambda functions" and they use -> instead of =>. Obviously there are going to be some differences in the details, but for our purposes, you can view them as the same. (note, they're also called anonymous functions)

A second peculiarity you'll notice is the Function<Integer,Integer> variable type. This is Java's way of overcoming the "functions can't be variables" we talked about in the previous post. This variable is a Function variable (just like Integer, or String). The syntax is a throwback to the OG functional programming languages as they define their functions similarly. What it's saying is "this is a function that receives an Integer type and returns an Integer type".

This allows Java to do things like have functions which take Integers and return functions that take Integers and return Integers (Function<Integer,Function<Integer,Integer>>), or functions that take functions and return functions that take Integers and return Integers (Function<Function<Integer,Integer>,Integer>> I could go on for days, here simultaneously having a bit of fun, as well as foreshadowing for my frustrations with the Java language below).

Yet another peculiarity is this .apply() business. JavaScript has this as well, and honestly I haven't looked into the difference between .apply() and simply calling the function in JavaScript, although I'm certain there is a difference. But anyway, that's all that's happening here. f1.apply(input) is calling our first function with the input, this is returning an Integer, which is passed into f1.apply(), thus calling f1, and, finally, we return the result when the f1.apply() call finishes.

The final peculiarity is, the .apply() method returns an object. We need to cast this object to an Integer which should be safe enough given we can see the function we're passing in returns an Integer, however, it would pay to be extra safe about this if you were writing any kind of serious code, especially because the parameter is simply a Function, and not a Function<Integer,Integer> specifically.

In the previous post I pointed out that JavaScript is a mess of a language. Here we see one of the differences between JavaScript and Java, and that's static typing (I've heard this joked about as "JavaScript: everything is everything!"). Here we are explicitly defining that the first two parameters passed into our composeFunctions method must be functions (ok, I'll admit, bad naming). In the case of the equivalent JavaScript code, we could very easily pass in an object in place of the function and we won't get an error until run time, this can lead to some nasty bugs, and a severe lack of readability when looking at other people's code. But I digress, onto the next example!

First Class Functions In Java

public static void testFunction(String name){

    Runnable innerFunction = () -> {
        System.out.printf("hello! ");
    };

    BiConsumer<Runnable, String> lateExecution = (Runnable inputFunction, String n) -> {
        inputFunction.run();
        System.out.println(name);
    };

    BiConsumer<Runnable, String> lateNoExecution = (Runnable inputFunction, String n) -> {
        System.out.println(name);
    };

    lateExecution.accept(innerFunction, name);

    lateNoExecution.accept(innerFunction,name);

}

We previously discussed the issues with JavaScript, so now let's bitch about Java. In the equivalent JavaScript code from the previous post it was extremely succinct and easy to follow what was going on. We were defining some inner functions, assigning them to variables and calling them. The code is literally running off the page in this Java example. In this Java case, you have to know what a Runnable is, and that it's an functional interface (whatever that is) that takes no parameters and returns none. You also need to know that a BiConsumer takes two parameters specifically, and returns nothing. Don't you dare try using the Function interface for either of these things. Don't think about calling apply() on the BiConsumer either.

The equivalent JavaScript took me 1 minute to write and didn't involve any googling or a compiler to double check. Sadly, I can't say the same for the Java. Obviously the functional aspects of Java were an afterthought (which is fine, it wasn't intended for that purpose anyway), and you get that feeling when trying to program functionally in it.

Remember in the previous post when I asserted that functional languages are great because the modularity is natural to a programmer, making it more probable to occur? When you have to memorize 10 different functional interfaces to get anything done, that reduction in cognitive load vanishes.

In the next post I'm thinking we'll look further into lambda functions, as well as the .map(), .filter(), and .reduce() higher order functions (used in the famous Hadoop big data framework). Until then!

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Logo
Center