Click here to Skip to main content
15,884,472 members
Articles / General Programming

An Introduction to Functional Programming with Java 8

Rate me:
Please Sign up or sign in to vote.
4.94/5 (7 votes)
14 Aug 2014CPOL6 min read 20.2K   16   2
Java 8 is perhaps one of the most exciting editions of the Java language in recent times. One of the headline features is support for functional programming which is the focus of this blog. The support comes mostly in three features: Support for [work pipeline] streams. Streams allow us to process d

Java 8 is perhaps one of the most exciting editions of the Java language in recent times. One of the headline features is support for functional programming which is the focus of this blog. The support comes mostly in three features:

  • Support for [work pipeline] streams. Streams allow us to process data through a number of stages in a pipeline in a functional way. We can create such streams from container instances such as List, or create them using specific stream classes such as IntStream.
  • Support for lambda functions. In simple terms this is an anonymous function (we don’t need to give it a name) which takes a number of parameters and returns a value. A returned value will be passed down the pipeline to the next operation.
  • Support for passing functions into other functions.

Given functional programming has been around since the 50s, and until recently mostly disregarded by the mainstream, why has it become such a hot topic? My opinion is that it’s because of its ability to easily process work in parallel taking advantage of multi-core processors, lazy (on-demand) evaluation, and ease of integration with other languages such as Java. Certainly the JVM has provided a good base for Scala which can even be embedded in a Java program, giving the best of both worlds plus an easier migration path for developers. Scala, however, has quite a steep learning curve, and there are already many existing Java programmers out there, so eventually there was pressure for Java itself to catch up to avoid having to learn a whole new language to do some cool and useful things.

Enough words, let’s get down to business and consider a hello world example:

public class HelloWorld
{
	public static void main(String[] args)
	{
		List<String> countries = Arrays.asList("France", "India", 
			"China", "USA", "Germany");

		for (String country : countries)
		{
			System.out.println("Hello " + country + "!");
		}
	}
}

This should be familiar to every Java programmer; we take a list of countries and print a greeting using an enhanced for-loop.

So, is there anything wrong just doing it like this? For a simple example, not a lot, but let’s imagine we were doing something more complicated.

We want to write some tests to check the code works, but testing the body of the loop might be tricky. We could extract the body into its own function of course, but if we did that everywhere we’d end up with lots of little functions for the bodies of loops.

What if we want to process items in parallel? We’d need to hand over a country to a thread that implements the greeting function. Should one thread process one country, or a batch of them? How are we going to pass the work in batches and check when it’s done? We have to also handle communication between the threads.

Another problem is that the loop body’s code is disjoint from the source of the loop variable. To see how a country gets created we must find the for statement. We’d prefer to tell the list of countries to do the message printing. The problem is that lists don’t understand being told to do things, they are just containers.

However, since Java 8, containers can pass their contents to an entity which does handle instructions – a Stream. I’ve changed the example to use a stream:

public static void main(String[] args)
{
    List<String> countries = Arrays.asList("France", "India", "China",
            "USA", "Germany");

    countries.stream().forEach(
            (String country) -> System.out
                    .println("Hello " + country + "!"));
}

The list of countries now creates a Stream and passes it a ‘spliterator’ from the container. This is an iterator which is capable of splitting the contents into batches of work. We’ll take a look at this batching (which then can be parallelised) in a future post.

Stream itself is an interface which is implemented by the abstract class ReferencePipeline. Jobs can then be chained to the returned Stream. Each job does some work, and perhaps calls a downstream job when it’s done. In the example the list creates a Stream and a forEach job is added to its pipeline. Like our loop in the first example, the forEach job does some work on each item. The funny -> indicates a lambda expression (an anonymous function), here taking a String named country and printing it in a message.

Since in this case the compiler can work out that country is a String, we can dispense with the type, and as there is just one parameter we can also dispense with the () around the lefthand side leaving the more concise:

countries.stream()
   .forEach(country -> System.out.println("Hello " + country + "!"));

Note it’s a lot clearer that we are doing work on countries rather than just referencing a loop variable which happened to be a country. It’s also easier to read as there is less boilerplate obscuring the actual business logic. Clearer and easier to read code is easier to understand, also it’s easier to spot mistakes. We don’t also have to worry about making mistakes in the boilerplate which cuts down our tests. Overall, coding is more fun and the development time is shortened.

Let’s experiment with this example a bit to illustrate what is going on under the hood. The lambda that foreach is taking is actually an implementation of Consumer<T>.

* Reminder for those a bit rusty or coming to generics for the first time that this means the Consumer type uses an unknown type T which is decided at compile time.

We can explicitly show use of Consumer by creating an inner-class:

private static class Greeter<T> implements Consumer<T>
{
	@Override
	public void accept(T t)
	{
		System.out.println("Hello " + t + "!");
	}
}

and changing the pipeline:

countries.stream().forEach(new Greeter<String>());

Consumer has an accept method which is called every time by forEach with each item in the pipeline.

Let’s separate the forming of the message and the printing into two jobs. To form the greeting message, we can use the map function. This applies a lambda function to an input transforming the input. The printing job is still carried out by a foreach:

public static void main(String[] args)
{
	List<String> countries = Arrays.asList("France", "India",
			"China", "USA", "Germany");

	countries.stream().map(country -> "Hello " + country + "!")
			.forEach(System.out::println);
}

Note the strange syntax System.out::println. We’re actually passing a function into forEach, the println method of the System.out instance. The :: syntax simply means pass the function on the right, calling with the object on the left. The left hand side can either be a class name for a static call, an object, or an alias (this or super) to an object. Note we cannot pass only the name of the function, since this would be taken to mean a variable whose type is-a Consumer.

Instead of a static main method, if we ran this inside an instance of an object which also had a doPrint method (taking a String and returning void), we could write:

countries.stream().map(country -> "Hello " + country + "!")
                 .forEach(this::doPrint);

Let’s look at how map works under the hood. Map takes an instance of a class which is-a Function and calls its apply method. Apply takes type T and returns a type U. We’ll create a Greeter inner-class to demonstrate this:

private static class Greeter<T> implements Function<T, String>
{
	@Override
	public String apply(T t)
	{
		return "Hello " + t + "!";
	}
}

...

countries.stream().map(new Greeter<String>())
                  .forEach(System.out::println);

When we used a lambda function here, it was just taken to be an anonymous instance of Function.

To finish, let’s run the pipeline inside an object has both a doPrint and a makeGreeting method (taking a String and returning a String), and see what it looks like:

public class HelloWorldConcise
{
	private void doPrint(String str)
	{
		System.out.println(str);
	}

	private String greet(String country)
	{
		return "Hello " + country + "!";
	}
		
	public void greetCountries()
	{
		List<String> countries = Arrays.asList("France", "India", 
			"China", "USA", "Germany");

		countries.stream().map(this::greet).forEach(this::doPrint);
	}

	public static void main(String[] args)
	{
		new HelloWorldConcise().greetCountries();
	}
}

The greetCountries function is very concise. It’s clear that we take each country, make a greeting from it and then print it. Another great thing is it’s really easy to test. If we extend the HelloWorldConcise class to make a HelloWorldConciseTest, we can implement our own versions of makeGreeting and doPrint. Our own makeGreeting can be used to check the greeting is being formed correctly after calling its parent version, and the doPrint method can be a stub.

* Okay, printing to stdout during a test is not a big deal, but imagine if doPrint sent the greeting to a web page, we wouldn’t want to have to set all that up in order to test it.

So that’s our first taste of Java 8 functional programming. I hope you found it useful. In the next article I will look at some other operations which can be included in the pipeline.


Revision History:

2nd July 2014 - Corrected the category

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionmmmm Pin
Sacha Barber14-Aug-14 10:53
Sacha Barber14-Aug-14 10:53 
But really functional, more like lambdas
AnswerRe: mmmm Pin
TheCannyCoder14-Aug-14 20:35
TheCannyCoder14-Aug-14 20:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.