Functional interfaces
I’m sure you’re aware that Java is not considered a functional programming language. It’s considered an object oriented programming language but with Java 8, we do have functional interfaces. With the introduction of Java 8, Oracle added two new packages, the java.util.function and the java.util.stream.The lambda expressions, also introduced with Java 8, use both of these new packages so we wanted to review some of the functional interfaces that are now available.
Definition of functional interface.
- It is defined as an interface that contains only one abstract function or method.
- Functional interfaces can represent abstract concepts such as functions, actions or predicates.
We will review some of the most commonly used functional interfaces in lambda notation but there are more interfaces available in the java.util.function package. To see a list of all the functional interfaces available, check out the Javadoc for java.util.function .
Some of the more commonly used interfaces include Predicate.
Predicates : Predicates are Boolean valued functions of one argument meaning they take in one argument, use a test method to evaluate it and return either true or false. Since this is an interface, we will have to override the test method with logic that determines what should be evaluated.
Consumer :The Consumer interface consumes the argument. It accepts a single argument and does not return a result.
Function : Function which transforms a value from one type to another. It accepts one argument and produces a result.
Supplier: Supplier supplies a value. It produces a result of a given type. Unlike Functions, Suppliers do not accept arguments but they do return a result.
UnaryOperator : UnaryOperator interface takes a single argument and returns a single value and the BinaryOperator interface takes two arguments and returns one.
Here, I’m using the Predicates interface.
//using the test method of PredicatePredicate<String> stringLen = (s)-> s.length() < 10;System.out.println(stringLen.test("Apples") + " - Apples is less than 10");
As you can see, in the angle brackets after the word Predicate I’m forming the interface to expect a String. It has a name of stringLen and it’s equal to on the right hand side is where I’m actually implementing my method.
I’m used lambda notation so it might look a little different than what you’re used to. On the right hand side in parentheses, I list either no arguments, a single argument or multiple arguments. For Predicate, I need to pass in a single argument then I have the arrow which is a hyphen(-) and a greater than sign(>). Make sure those two are together. After that, I have the logic of my Predicate. So in this case, I’m saying if the string.length or the length of the string is < 10, return true. Otherwise, return false. For each of my examples, I print out the result. So calling stringLen.test() with the value “Apples” and it’ll print out true or false.
Here, I’m using the Consumer interface.
//Consumer example uses accept methodConsumer<String> consumerStr = (s) -> System.out.println(s.toLowerCase());consumerStr.accept("ABCDefghijklmnopQRSTuvWxyZ");
Remember, the Consumer interface accepts a value but does not return anything. So on the right hand side, I’m passing it, the argument s but the actual method itself is just going to print out that value in all lowercase letters. It doesn’t return anything.
Now, I have a Function interface which has two values in the angle brackets.
//Function exampleFunction<Integer,String> converter = (num)-> Integer.toString(num);System.out.println("length of 26: " + converter.apply(26).length());
The Function interface, the first value represents the value coming in for the argument and the second value represents the return value. So in this case, I’m passing in an integer and I’m going to return a string. The logic that I use to do that is on the right hand side of the arrow that says integer.toString() num where num is my argument, I’m going to pass in a value of 26 which gets converted to a string which will just be the characters 2 and 6 which will have a length of 2.
Here is my Supplier interface.
//Supplier exampleSupplier<String> s = ()-> "Java is fun";System.out.println(s.get());
Remember, the Supplier does not contain any arguments. That’s why I have the open and closed parentheses with nothing inside. It’s important to make sure that you understand you have to include the empty argument list. Otherwise, the compiler will think you made a mistake. So we have an empty argument list, our arrow and on the right hand side, I have a string that says “Java is fun”. It uses the get() method which is the functional method for Supplier to get the value of the string and I have that inside of a print line statement so it’ll print it out.
//Binary Operator exampleBinaryOperator<Integer> add = (a, b) -> a + b;System.out.println("add 10 + 25: " + add.apply(10, 25));
The BinaryOperator is going to return an integer. On the right hand side, you can see it expects two values, a and b. It’s going to then add them together and return those values. Now it is so far that none of my arguments have their data types explicitly mentioned. That’s because when we’re using lambda notation, it is inferred. So since the BinaryOperator is returning an integer, it knows that the two values coming in as arguments, a and b, must also be integers. I’m using the apply method of the BinaryOperator to add 10 and 25.
//Unary Operator exampleUnaryOperator<String> str = (msg)-> msg.toUpperCase();System.out.println(str.apply("This is my message in upper case"));
The UnaryOperator is going to return a string. It takes in a string( msg) the message, and it converts it to uppercase. On next line, I’m going to print that out using the UnaryOperator method .apply().
Here is my full Java class:
In the Output window,
the first was the Predicates which returned true that the string Apples is less than 10 characters.
Next, I used the Consumer interface to take in a series of letters representing the alphabet in all upper and lowercase letters mixed. It used the two lower to print it back out in all lowercase followed by the Function interface which took in an integer, 26, converted it to a string of two characters, two and six and then found the length of that string which was two.
The Supplier actually supplied the string, Java is fun. So you can see in line 31 right above, I’m using the s.get. It gets the value, “Java is fun” and prints it to the command line followed by the BinaryOperator which took 10 and 25, added them together and gave us 35 and the last one was the UnaryOperator that took in a message that was in mixed case and returned that message in all uppercase.
So these are a few of the more commonly used functional interfaces available with Java 8. Remember, there are a lot more that you can use so don’t forget to check out that Javadoc on java.util.function.
Lambda syntax
If you’ve ever written programs that involve threads, you’re familiar with the concept of a Runnable interface.
The Runnable interface does not take any arguments, and we can use lambda notation to help us write the inner class needed to implement this interface.
// Anonymous Inner Class: RunnableRunnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("run 1");}};
Here, I’m using the old style of creating an anonymous inner class.
// Lambda version of Runnable (no arguments)Runnable r2 = () -> System.out.println("run 2");
It is the new version of implementing that inner class using Runnable r2 equals. Open and close parentheses to indicate no arguments, and we’re printing out run 2.
// Start both threadsr1.run();r2.run();
Now, we start r1 and r2 by using r1.run(), r2.run(), and that’ll initiate those methods.
I have some additional examples so you can see some more lambda syntax.
//using an existing functional interface BiFunctionBiFunction<String, String, String> concat = (a, b) -> a + b;String sentence = concat.apply("Today is ", "a great day");System.out.println(sentence);
Here is the BiFunction interface, which has angle brackets with three string values inside. This indicate the two values that are coming in are going to be strings, and the return value will be a string. On the right-hand side is what we call our lambda notation: parentheses around the two arguments, (a, b) , our arrow notation (->), and then a + b. When you use a + b and you have strings, it puts them together. I use the concat.apply() to put together “Today is a great day”.
//example of the Consumer functional interfaceConsumer<String> hello = name -> System.out.println("Hello, " + name);for (String name : Arrays.asList("Duke", "Mickey", "Minnie")) {hello.accept(name);}
Here, I’m showing a consumer interface. In this case, we’re passing in a name and printing out hello with that name. I have it set up so that string name is going to take each name in the Arrays.asList() with Duke, Mickey, and Minnie, and then it’ll invoke the hello.accept(name), which’ll print
Hello, DukeHello, MickeyHello, Minnie
And finally
//example of passing one valueGreetingFunction greeting = message ->System.out.println("Java Programming " + message);greeting.sayMessage("Rocks with lambda expressions");}//custom functional interface@FunctionalInterfaceinterface GreetingFunction {void sayMessage(String message);}
Now, we have an example of passing one value. We have GreetingFunction greeting = message. message is the one parameter that’s getting passed. Notice that, we’re actually creating the functional interface called GreetingFunction. I wanted to show you how you can create your own interface, as well as use the existing ones in the java.util.function.
The class is given below:
Now if you run this program, you’ll see the first two threads print out as:
I hope these examples are starting to make sense as far as the syntax of a lambda notation.
Methods as lambdas
Another feature of Java 8 is we can now take any type of method and convert it into a lambda including static method, instance methods, and even constructors.
Do you notice any syntax that looks a little different? If you’ve programmed in C++ before you might recognize this symbol that I’m referring to. Notice on the right hand side where the lambda notation would normally be, we have integer::ToString. This is the structure that creates a lambda from a method. It’s called a method reference and it enables us to pass references of methods or constructors via the :: syntax.
IntFunction<String> intToString = num -> Integer.toString(num);System.out.println("expected value 3, actual value: " +intToString.apply(123).length());
we have the interface int function which is going to return a string and take in an integer. It’s a special function in function, we don’t have to specify the data type of our argument num because the interface provides enough information to the compiler for it to infer that num is going to be an integer. On the right hand side of our arrow we have integer.toString which takes num, converts it to a string.
Next we’re printing out the value, intTwoString.apply where apply is the functional method of the int function interface. It’ll convert the number, 123, to a string and get the length.
//static method reference using ::IntFunction<String> intToString2 = Integer::toString;System.out.println("expected value 4, actual value: " +intToString2.apply(4567).length());
And as I stated we’re using this new method reference, the ::. So this time we don’t even specify the variable name or the argument on the right hand side. We just have integer::toString. Now the compiler is smart enough to know that it should expect an integer and return a string. I’m actually going to use the 4567 which is an integer which will be converted to a string.
There’s a few more examples.
//lambdas made using a constructorFunction<String,BigInteger> newBigInt = BigInteger::new;System.out.println("expected value: 123456789, actual value: "+newBigInt.apply("123456789"));
I’m using the function interface which has two values in the angle brackets. It’s saying I’m going to give you a string and I want you to return a big integer.
On the right hand side I have an example of how we can use the colon colon with a constructor. So it’s saying that whatever string is coming in I’m going to create a new big integer. So I’m going to convert the string value to a big integer. We use the .apply for the function interface and I’m giving it a string that has the values 123456789 and it will convert that to an integer.
//example of a lambda made from an instance methodConsumer<String> print = System.out::println;print.accept("Coming to you directly from a lambda...");
This is the Consumer interface. Remember the Consumer interface consumes data but does not return anything. So I have print.accept(). That is the method name of the functional method for the Consumer interface. And I’m giving it the string coming to you directly from a lambda. Now on next line in the right hand side notice the print line is an instance method of system.out. It will automatically take in the string, provide it on next line and print it to the console.
//these two are the same using the static method concatUnaryOperator<String> greeting = x -> "Hello, ".concat(x);System.out.println(greeting.apply("World"));UnaryOperator<String> makeGreeting = "Hello, "::concat;System.out.println(makeGreeting.apply("Peggy"));
The last example is the unary operator which is going to operate on a string. It has the value hello::concat. The concat is the method that I’m actually going to be using, and again I’m not specifying the fact that I’m going to provide a second string. It can infer that.
Let’s run the program and take a look at the output.
As you can see in Java 8 it’s starting to make things a little simpler as far as the amount of code that we need to write. In this particular example we were making methods into lambdas whether those methods were static methods, instant methods or even constructors.
Create new functional interfaces
Although Java 8 has included a new package, java.util.function, that includes many different functional interfaces, sometimes we just need to create our own functional interface.
We can create a new functional interface by creating a new file or we can include the functional interface inside of our class file. Either way when creating our own functional interfaces it’s helpful to add annotation that says @FunctionalInterface to help ensure this is a functional interface.
As you can see
@FunctionalInterfaceinterface Calculate {int calc(int x, int y);}
I have the at functional interface. Now this particular interface has only one method header. A functional interface is an interface that has only one method.
The only code that I need in here is the name of the interface and then the method header or the method signature. In this case i, int calc is the name of my method and it takes two parameters that are both integers. I do need to specify the data types for each value here. The return type and the two arguments.
Calculate add =(a,b)-> a + b;Calculate difference = (a,b) -> Math.abs(a-b);Calculate divide =(a,b)-> (b != 0 ? a/b : 0);
This is really nice because my interface is very simple. It simply has the interface name and the one method signature. And then using the lambdas I can define that method signature in different ways. Add, difference, divide and multiply.
Collections
Since collections are used so heavily in Java 8 lambda notation, I wanted to do a quick review. The Collections API was introduced with Java 7.
A collection is an object that groups multiple elements into a single unit.
Collections are used to store, retrieve, manipulate, and communicate aggregate data.
The collections API provides the following interfaces:
set : which is a collection that does not contain duplicates.
list : which is just an ordered collection based on the way the user entered the data.
map : IT is an object that maps keys to values.
The collection interface contains methods that perform basic operations such as
int size() : which gets the size of the collection.
boolean isEmpty() returns true if it’s empty, false if it’s not.
boolean contains() : checks to see if an element is inside the collection, returning true if it is and false if not
add() : an element returns true if it was able to add the element, or false if there was an error.
boolean remove() : which tries to remove an element. If the element is not found, it returns false. If it successfully removes it, it’ll return true.
iterator() : it returns an iterator over the elements in this collection.
Now let’s review some examples of collections.
I have a program that lists some collections. I’m going to start by using collections and not using the lambda notation, then I’m going to show how the lambda notations can simplify our code.
I create a list of names.
List<String> names = Arrays.asList("Paul", "Jane", "Michaela", "Sam");//way to sort prior to Java 8 lambdasCollections.sort(names, new Comparator<String>() {@Overridepublic int compare(String a, String b) {return b.compareTo(a);}});
I have four names in my list. In order to sort those, prior to using the Java 8 lambdas. we could say Collections.sort() and provide the variable names and new Comperator, and then we’d have to override the collections interface, the method compare, to tell it how to do the compare.
//first iteration with lambdaCollections.sort(names, (String a, String b) -> {return b.compareTo(a);});
Now I have the next iteration which uses lambda, but it’s still not quite the most concise.
//now remove the return statementCollections.sort(names, (String a, String b) -> b.compareTo(a));
This is the best version of this code using lambda notation. Remember we don’t have to specify the data types in the lambda notation because it can be inferred since there is only one method inside the Collections interface.
//now remove the data types and allow the compile to infer the typeCollections.sort(names, (a, b) -> b.compareTo(a));
In this case the sort method takes in a list called names, it takes in two values, a and b, and compares b to a, returning them in the right order.
All right, next I wanted to show you how we could do a book collection. So I’ve created three random books here.
//total pages in your book collectionBook book1 = new Book("Miss Peregrine's Home for Peculiar Children","Ranson", "Riggs", 382);Book book2 = new Book("Harry Potter and The Sorcerers Stone","JK", "Rowling", 411);Book book3 = new Book("The Cat in the Hat","Dr", "Seuss", 45);
I do have a Book class, so let’s take a quick look at that.
public Book(String title, String authorFName, String authorLName,int pages) {this.title = title;this.authorFName = authorFName;this.authorLName = authorLName;this.pages = pages;}
In my Book class I have a constructor that takes all the values and then I have all my get and set methods.
I’m adding the three books that I created above.
List<Book> books = Arrays.asList(book1, book2, book3);int total = books.stream().collect(Collectors.summingInt(Book::getPages));System.out.println(total);
On above I wanted to show you how we could use the .collect. So the .collect uses the Collections, so it uses Collectors.summingInt and it uses the getPages method of the Book class to get the number of pages. It adds them all together and puts them into the variable total and then prints them out.
//aggregate author first names into a listList<String> list = books.stream().map(Book::getAuthorLName).collect(Collectors.toList());System.out.println(list);
Now I am creating another list = books.stream, I’m using the .map to get the last name of the author. Notice I’m using our method reference that we talked about earlier, the ::, Here I’m using the .collect again, this time instead of adding together the values of an integer variable as getPages, this time I’m putting a list together of the authors’ last names and then I’m printing out the list.
The next example creates a list that has duplicate books.
//create a list with duplicatesList<Book> dupBooks = Arrays.asList(book1, book2, book3, book1, book2);System.out.println("Before removing duplicates: ");System.out.println(dupBooks.toString());
So on the right hand side I just added book1 and book2 in a second time. I’m printing them out so we can see what it looks like before we eliminate the duplicates.
Collection<Book> noDups = new HashSet<>(dupBooks);System.out.println("After removing duplicates: ");System.out.println(noDups.toString());
Then next I’m creating a new collection and this time I’m using a HashSet. Remember, the set interface will automatically eliminate duplicates, so next I’m printing out this new list of books.
Finally, I wanted to show you how we could use the set interface, and this time I’m also using HashSet,
//example of using Set to eliminate dups and sort automaticallySet<Integer> numbers = new HashSet<>(asList(4, 3, 3, 3, 2, 1, 1, 1));System.out.println(numbers.toString());
I’m providing a list that has several numbers, and many of them are repeated. What will happen is it will actually take the values in the list and it will eliminate all the duplicates and then print it out.
Let’s run the program to take a look.
At the very top, remember we added up the total pages of all the books?
Next we use the .collect() to collect the last names of the authors and printed those out, so we have Riggs, Rowling, and Seuss.
Next I print out the names of the books and the authors for each of the books in our list.
Then when I created the set using the exact same list, notice this time it removes the duplicates, because the set interface does not allow duplicates.
And finally, the very last thing I did was use a list that had the numbers 4, 3, 3, 3, 2, 1, 1, 1, and when I used the set interface, it automatically removes duplicates and puts them in sorted order.
In JDK 8 and later, the preferred method of iterating over a collection is to obtain a stream and perform aggregate operations on that.
Aggregate operations are often used in conjunction with lambda operations to make programming more expressive and using less lines of code. For more information on collections, check out the Oracle tutorial
Streams
The java.util.stream package contains the majority of the interfaces and classes for the stream functionality. A stream in this case represents a sequence of elements. The stream package was added in Java 8 specifically to help traverse collections. Most stream operations accept some kind of lambda expression parameter which is a functional interface specifying the exact behavior of the operation.
Stream operations are categorized as either intermediate or terminal.
Terminal operations are either void or return a result of a certain type.
Intermediate operations return the stream itself. Intermediate operations are useful to allow us to chain multiple method calls in a row on a single stream.
Some of the commonly used operations include map, filter and forEach where map and filter are intermediate operations. They allow us to map one value to another or to filter the results using a Predicate. forEach is a terminal operator.
Two more commonly used operations are sorted and collect.
Sorted is an intermediate operation which returns a sorted view of the stream but remember, the original collection is not being changed.
Collect is an extremely useful terminal operation to transform the elements of a stream into a different kind of result. Remember, elements in a collection cannot be changed or mutated with streams but you can save them to a new collection if that’s what you need.
Let’s walk through the examples
Arrays.asList("red", "green","blue").stream().sorted().findFirst().ifPresent(System.out::println);
I’m using Arrays.asList to create a list that includes three strings, red, green and blue.
It is using the .stream() to create a stream of those three strings. Then it is using the sorted() operation to sort them. It is using findFirst(). So which value will it find? the stream is going sequentially through these operations. So it sorted it first. Now, it’s going to find the first element which will be blue it is going to actually print that out.
The check next example
// example of Stream.of with a filterStream.of("apple", "pear", "banana", "cherry", "apricot").filter(fruit -> {// System.out.println("filter: " + fruit);return fruit.startsWith("a"); // predicate})// if the foreach is removed, nothing will print,// the foreach makes it a terminal event.forEach(fruit -> System.out.println("Starts with A: " + fruit));
This is another way to create a collection using a stream. In this case, We have five different fruits, apple, pear, banana, cherry and apricot. I wanted to use the intermediate filter operation. Next, I used .filter() and you can see, here’s out lambda expression. I take each fruit which is going to represent a string and I return fruit.startsWith a. Next, I’m using the terminal operator .forEach() which is going to take in the fruit that starts with an a because I used the filter to get rid of everything else and it’s going to print out the fruits that start with “a”.
The forEach makes it a terminal operation so the code actually invokes the filter and then the forEach. Otherwise, it will skip over the filter operation.
And finally, I have another string collection that contains two strings, “Java” and “Rocks”.
//using a stream and map operation to create a list of words in capsList<String> collected = Stream.of("Java", " Rocks").map(string -> string.toUpperCase()).collect(toList());System.out.println(collected.toString());
I’m mapping those strings to all uppercase. Again, there’s another lambda notation in the .map operation.
I used the .collect() which is a terminal operator to allow me to retain whatever values came into the .map() operation. In this case, I am creating a new collection called collected and I’m printing that out on last line.
The whole class is :
Let’s run this program and see what the results look like.
So it’s important to notice how each value goes through only one time. It’s not going to search through the entire list looking for fruit that start with a and then go through and print them out. It’s actually going to go through one, find the ones with a and pass them on to the forEach(). These are just a few examples of this powerful new feature that allows us to work with collections by using stream operations.
Primitive streams
In Java 8, in addition to the streams that we mentioned earlier, it also includes special streams for some of the primitive data types, specifically int, long, and double. They are int stream, long stream, and double stream. I have a sample program that shows some of these functionalities.
Primitive streams use specialized lambda expressions as well. We have, for example, int function instead of function, or int predicate instead of predicate. And primitive streams support the additional terminal operations of sum and average.
Let’s walk through these examples, and then we’ll run the program to see how it works.
IntStream.range(1, 4).forEach(System.out::println);
Here, I’m using the new int stream. I’m using a .range() to get the values from 1 to 4, but not inclusive of the 4. So it’d only get the values 1,2 and 3. Next I’m using the terminal for each operator, which’ll print out those values.
Now, I wanted to show you some of those aggregate operations that are available with these primitive data types, the .average().
//find the average of the numbers squaredArrays.stream(new int[]{1, 2, 3, 4}).map(n -> n * n).average().ifPresent(System.out::println);
Here, I create arrays.stream, and I create a new int[] that has four values. Next, I’m showing how you can use the .map(), which’ll take each value, and it’ll actually put back into the stream that value squared. Next, I’m going to use the .average(), which’ll add up all my new values in the stream, and in this case divide them by 4, since there’s 4 elements. And then next, I’m using the .ifPresent() to print out the results.
//map doubles to intsStream.of(1.5, 2.3, 3.7).mapToInt(Double::intValue).forEach(System.out::println);
Here, I’m creating a stream that includes doubles. So I use stream.of(), and since there’s 3 double values in the parentheses, I am getting a double stream. Next line, I have .mapToInt(). I can take a double and convert it to an Int. And then next, I’m printing out the results. What do you think’s going to happen. If you take a double and you cast it as an int, it truncates the decimal portion. All right, let’s go ahead and run the program, and see the results.
The first set of numbers, 1, 2 and 3, is from the int stream .range.
Next, we have 7.5, which is the average of addition.
And then, finally, we can see that the stream of doubles, the double stream, when I mapped it to int, it took the double value and converted it to an int value, and it did truncate, as we thought. And then the four each printed out.
Next steps
So what do you think of Lamda Expressions? the clarity of the code and the reduction in the amount of code is very appealing. I hope you enjoyed the post. Remember to always be a life long learner, keep programming, and have fun while doing it. Check back often to see what new and exciting posts are waiting for you to dive in and explore.