In this article of the “Java SE 8 new features tour” series, we will deep dig into explanation, and exploring the code, on how to traverse the collections using lambda expression and with method references, filtering them with predicate interface, implementing default methods in interfaces, and finally implementing static methods in interfaces.
In the previous article “Functional programming with Lambda Expression”; I have deeply dived into understanding lambda expressions. I have shown you a few different uses of Lambda Expressions. They all have in common the implementation of functional interfaces. I have also explained how the compiler is inferring information from code, such as specific types of variables and what is really happening in the background.
Source code is hosted on my Github account: Clone it from here.
Table of Content:
- Traversing collections with lambda expressions.
- Filtering collections with predicate interfaces.
- Traversing collections with method references.
- Implementing default methods in interfaces.
- Implementing static methods in interfaces.
1- Traversing collections with lambda expressions.
In Java SE 8, you can use lambda expressions to traverse a collection of items. Examples of collections include lists, maps, and sets. All of these data types implement an interface called iterable. To understand the code I'm about to show you, let's start in the documentation. I'm working a class called TraverseFileContent.java
under eg.com.tm.java8.features.lambda2
package. I'll right click into the method name readAllLines
and then Show Javadoc.
It returns List
class that extends a number of interfaces. This is the one I'm interested in called iterable. This interface was added in Java SE 5. And it has a method called iterator
. That returns an instance of an Iterator
interface that you can use then to loop through the contents of a collection. But in Java SE 8 there were a couple of new methods. One called forEach
, and one called spliterator
. I'm going to focus on the forEach method.
It accepts an instance of a functional interface, called Consumer
. The consumer interface has a single abstract method, named accept
. And by implementing this interface and its single abstract method, you can add code that operates on an item in the collection.
So, let's go back to the code. In the TraverseFileContent.java
class, I'm traversing this lines collection, this array list of strings representing the contents of a file, twice. In first version, starting at line 51, I'm using a forEach
loop, it's a simple bit of code, and it creates a line String variable for each item in the list and then executes whatever code is needed.
At line 58, I'm using another approach, calling the iterator method of the collection. Getting an iterator object and then looping with a while code block, calling the iterators has next method. Now both of these bits of code have worked fine going all the way back to Java 5, but I'll show you what this might look like with a lambda expression and the forEach method. In Java 8, I'll start with this forEach method.
Starting at line 65 I'll reference my lines collection and call the new forEach
method. And again it's going to receive an instance of the Consumer interface. Because Consumer is a functional interface, I can use a lambda expression. The Consumer interface's accept method requires a single argument of the appropriate data type. Because lines variable is declared as a list of strings, I have to pass in a string as the argument.
I'll pass it in with a name of line. Then I'll add my lambda operator, the arrow token. And I'll add my system output here, and pass in line.
And you can clean up the code a bit by removing old traversals. And now this single line of code replaces the three lines of the forEach
loop. I'll make a copy of this code and move down here to the version that's using an iterator. And I'll select those lines of code and comment them out. And paste in my lambda expression version. So when you replace an iterator, you're replacing four lines of code with a single line.
More advanced you can use Stream
with collections, Streams supports a sequence of elements supporting sequential and parallel aggregate operations. Normally it is used as the following:
Now save and run the code and see that it behaves exactly the same as it did before, but just with less code. So this is just another option for traversing collections. You can use forEach
loops. You can use iterator objects and now you can use the forEach
method with a lambda expression.
2- Filtering collections with predicate interfaces.
In addition to the new lambda syntax, Java SE 8 adds a number of new functional interfaces. One of the most useful is called the Predicate
Interface. An interface that has a single boolean method named test
, which you can use to wrap up your conditional processing, and make conditional code a lot cleaner. I'll show you how to use the predicate interface in this project.
Starting in the class named FileDirFilter
class in package eg.com.tm.java8.features.lambda2.predicate
, I've declared a method called predicateInInnerClass
.
I will use NIO.2 DirectoryStream
class, to get a specific path entries stream, which in our case is declared as:
Now my goal is to filter this stream and only display certain entries, which are directories. You can use the predicate interface either with inner class syntax or with lambda syntax.
I'll start with inner class syntax. In predicateInInnerClass
method, I'll declare an instance of the predicate interface. I'll type the name of the interface and press Ctrl+Space. And choose the interface from the list. It's a member of the package Java.util.function
, and you'll find a lot of other new functional interfaces there as well. This interface needs a generic type declaration. I'll set that to the name Path
class. And I'll name the predicate, dirsFilter
.
Now, for inner class syntax, I'll start with the new keyword, and I'll press Ctrl+Space and choose the constructor for the predicate interface. When I choose that, NetBeans automatically implements the single abstract method, test
. Because I declared my predicate with a generic type of Path
, the test method accepts a single argument of that type as well. Now, I'm going to implement the method. I'm going to change the name of the path object being passed in to just "t".
I'll set the return statement to use a very simple criteria. I'll add a set of parentheses to wrap my condition. And I'll set the condition to isDirectory(t, NOFOLLOW_LINKS)
. So now my predicate object encapsulates my test, and the test method can be used to determine whether I want to process an object from my collection.
The next step is to traverse the collection. And you can do this in a number of ways with a forEach
loop, an iterator object, or with the new forEach method.
I'll use the classic forEach
loop. I'll type foreach and press Ctrl+Space, and choose the foreach code template. Within the for loop, I'll use an if
statement. And I'll set the condition to use the dirsFilter object that I just declared. I'll call dirsFilter.test
. I'll pass in the file object that I'm declaring in foreach loop. And then if the condition is true, I'll use system output and I'll output the results of calling the getFileName
method of the file
object.
I'll save and run that code. And I only see full path of paths of type directory. Now, if I want to change my condition, I can change the predicate object and rerun the code and it'll work fine. But our goal here is to make the code as concise and readable as possible. And for that, you might decide instead to use a lambda expression to implement this predicate interface. So let's go back to the code, and I'll fold this method, predicateInInnerClass
, and unfold other method, predicateWithLambda
.
Now I'm going to do very similar processing. But I'll declare my predicate object this time using a lambda expression. Once again, I'll type the name of the interface. I'll press Ctrl+Space and choose it from the list, and set its generic type. And I'll name this predicate object noFilter
. Now I'll implement the interface using a lambda expression. I'll start with the signature of the method I'm implementing. That's the test
method. And because I declared predicate with a generic type of Path
, the argument is going to be an instance of path.
I'll just name it p
. Then I'll add the arrow token, and then I'll implement my method with a simple conditional expression, to true
to just return all defined path entries. And that's all I need. This single line of code replaced five or six lines of code in the inner class version. Next, I'll loop through the list and use the test method of the predicate object. This time, I'll use the doFilterAndPrintPath
method passing in my predicate noFilter
.
I showed how to use this method in previous lambda implementations. It's a new method that was added in Java SE 8. In implementation of doFilterAndPrintPath
method I am using the forEach
method of newDirectoryStream
returned collection of paths, I'll start with the name of the object I'm passing in as an argument. This time I won't wrap it inside parentheses just to show you a difference in syntax. Then I'll add the arrow token and a pair of braces. Within the braces I'll use an if
statement. And I'll pass in my condition, which again the predicate's test
method. I'll use pred.test
, and I'll pass in the path
object.
And if the condition is true, I'll use system output. And I'll output the path object file name. I'll save and run the code. And there is the result. Once again, I'm displaying all entries. But what if I want to deal with more than one possible condition? Well this is the great thing about lambda syntax and the predicate interface.
You can make as many predicate objects, as you want, each representing a different condition.
So I'm going to duplicate this line of code, and I'll change the name of the new one to hiddenFilter
. And I'll change its condition to show only hidden files and directories. To make it really easy to pass in whichever predicate I want, I'll take this code that's traversing the collection, and put it into its own separate method. We already did that in doFilterAndPrintPath
method.
Now, the method doesn't know which predicate it's going to get, so I'm refactored the name of the predicate object in the method to just pred
. And now, my existing code is passing in hiddenFilter
, so I'll run that. And I get all hidden files and directories. And then I’ll change the predicate object that I’m passing in to timeFilter
, and I’ll run the code again.
And this time I get all files and directories that is modified today. So this is how you can use the new predicate interface and lambda expressions to encapsulate your conditions in individual objects. And then pass those objects into your own methods for processing.
I won't cover a lot of the other new functional interfaces in Java SE 8 in detail, but I encourage you to investigate the package that the predicate interface is a part of java.util.function
. You'll find a lot of new useful interfaces there.
And because they're all functional interfaces, they can all be implemented with lambda expressions.
3- Traversing collections with method references.
In addition to Lambda expressions, Java SE 8's Project Lambda also adds method references to the language. A method reference gives you a way of naming a method you want to call, instead of calling it directly. And just like Lambda expressions, the goal is to make your code more concise and more readable.
I'll demonstrate this in this class named MethodReference
under package eg.com.tm.java8.features.lambda2.mthdRefs
.
You can use method references on four kinds of methods:
- Static methods of any class.
- Instance methods of a particular object.
- Instance methods of an arbitrary object, in which case you would refer to it just like it was a static method.
- And references to constructor methods.
I'll start with static methods. In this code, I have a FilesDirTests
class, and my goal is to check wither a specific file is accessible or not. I'm going to create a method that does some comparisons to a path class. Now you could put this method anywhere you like, and developers will differ about the best place to put it, but I'm going to create the method as a static method of my FilesDirTests
class.
I'll open the class and I'll place this new method. I'll declare it as public
and static
, and set its return data type to boolean
. I'll name the method isAccessible
. And the method will accept reference to path class. I'll name it p. This method will know that the goal is to compare path against some accessibility methods defined in java.nio.file.Files
class. And just like the test
method of the Predicate
interface, this will return a true if the path is accessible, or false to indicate that path is not accessible.
I'll save that change, and now I'll go to my class MethodReference
. To test accessibility of a given path files, I once again use the doFilterAndPrintPath(Predicate<Path> pred)
method. I'll call it inside doPrint
method. The defined path is used inside doFilterAndPrintPath
method. And for the predicate object, I'll use a method reference. It'll look like this, I'm referring to a static method, and so I’ll start with the type, the class that contains the static method.
Then I'll put in the double colon operator, that's how you separate the type or the object from the name of the method that you're calling. And then I'll pass in the name of the method, isAccessible
. Now here's why this is working. This method, the doFilterAndPrintPath
method, is expecting an instance of the predicate interface. That interface has a single abstract method, which expects one value. I'm calling a method that expects one value. And returning a data type that can be used by the test method.
The doFilterAndPrintPath
will traverse the path files, and output the values according to the test. Save the change, and run the code, and there's the result. Opppps There is no result? No files printed? This is because isAccessible
has test condition that will make the test fails, which is isExecutable
method.
So that's a static method reference. If you prefer, you can use method references with instance methods. To do this, I'm going to go down at the end of MethodReference
class and add the following two methods:
In this class, in the main method, I'm creating an instance of the current class, and then calling it doFilterAndPrintPath
method. By the time I get down here, all of the data and the methods are instance members, rather than static members.
Because main is a static method, so we can’t use this
keyword, as alternative syntax you can use this keyword as reference inside object instance methods.
And just as I did before, I'll save and run, and there's the result. So method references are a simple way of making your code incredibly concise.
4- Implementing default methods in interfaces.
Prior to Java SE 8, interfaces could contain abstract methods and constant declarations, but you couldn't provide fully implemented methods that would be inheritable.
I'm working in a package called eg.com.tm.java8.features.lambda2.defltMthd
. And in this application, I have an interface named VehicleInterface.Java
. It has eight abstract methods, in interfaces all abstract methods are soon to be public, so I haven't included the public
keyword and these are basic getters and setters.
Then I've a class named Car.java
and this class has the setters and the getters implemented. And a constructor method that makes it easy to instantiate the class.
And then I have a main class called DefaultMethod.java
. In this code, I'm filtering a list of cars using a predicate object, and then, display cars. I'm putting together a string named info and outputting it to the console. So, I'm going to refactor this code using a new capability of Java SE 8 that lets me add something called a default method to an interface.
When you add a default method to an interface, you can add its full implementation. And then, any class that implements that interface will inherit that method and you can call it itself or the method will be callable from anywhere else in the application because, just like the abstract methods, it will be public.
Back to VehicleInterface.Java
I'll move the cursor below the abstract
methods. And I'll start my method signature with the new keyword, default
. The rest of the method will look exactly the same, as if I were implementing it in a class.
I'll start with the return type, then the name of the method. Next, I'll add the code, and it'll be a return statement that can concatenate the values of the name, model, car CC and the make year. Now, because this is an interface, I can't refer to private fields. You can't do that.
So, I'm just going to refer to the abstract methods, which I know will be implemented by the class itself. I'll call getName
, getModel
, getCC
. Then I'll concatenate getMakeYear
and a closing parenthesis.
I'll save that change and now that method is available to every class that implements the interface. I won't make any changes to the Car class. It already has that method. Then I'll go over here to my main class, use default method, and I'll change this code. I no longer need to create the string named Info, that's going to be done by the new method that the Car class has inherited. So, I'll comment out that line of code. And I'll replace the reference to the info variable with a call to the getInfo
method.
But I'll call it as a member of the car object that I'm working with right now. I'll save the change and run the code. And there's the result. I'm successfully calling the getInfo
method to get a string, a concatenation of the values of the name, model, CC and make year, and then I'm outputting it to the console using the code in my main class.
By using default methods, you can sometimes eliminate a whole layer of inheritance. Some developers, for example, in earlier versions of Java, might have created an interface, then a base class that implemented the interface and then a subclass that they would actually use in their code.
With this new capability, you might not need the base class at all and instead, can go directly to implementing the subclasses, inheriting the default methods directly from the interfaces.
5- Implementing static methods in interfaces.
I previously described how to add default methods to interfaces, which are fully implemented and are inherited by implementing classes. In Java SE 8, you can also add fully implemented static methods to interfaces. Just as with default methods, the goal is to let you eliminate inheritance layers and simplify your applications.
I'm working in a package called eg.com.tm.java8.features.lambda2.staticMthd
. Just as in the earlier project, the one for default methods, the main class, which is called StaticMethod
here, has code that gets the name, model, CC and make year from a car object.
It's at line 47 in this class. My goal is to take this code and move it to a static method, but instead of adding it to a base class or other concrete class, I'm going to add it to an interface. Something I wasn't able to do in earlier versions of Java. I'll take this code and copy it to the clipboard. Then, I'll open the interface, named VehicleInterface.java
. Just as before, I'm starting with a set of abstract method declarations. Now, I'll place the cursor after those abstract method declarations and I'll start with the keyword static
.
Just as with the default method and the abstract methods, this will be a public method automatically. I don't need to declare it. It'll return a string and I'll name it getVehicleInfo
. Now because this is a static
method, it can't refer to the instance methods declared above. So, I'm going to pass in an instance of the Car object. Then I'll provide the return keyword and I'll paste in the code from before and I'll clean this up so that now I'm returning the name
, model
, CC
and make year
and the closing parenthesis.
Now this static
method is available from anywhere in the application. Just as before, I don't need to do anything to my Model class. That's called car
, because all the code that's going to be getting the string and outputting it, is already available in the interface.
I'll go back to my main class, use static method, and now, instead of putting together this string here, I'll call the interface using the interface's name, VehicleInterface.java
, then I'll call the new static method, getVehicleInfo
and pass in the Car object, which is c
.
And I'll save the change and run the code. And there's the result. Functionally, it's exactly the same as using a default method, or putting this code into the Car
class, or having it right here at the top level in the main class.
The goal of both default and static methods is just to give you more options in putting together the inheritance model for your application. Using both default and static methods, you can eliminate entire inheritance layers of your application and enormously simplify the coding model, making the application easier to code and to maintain.
Resources:- The Java Tutorials, Lambda Expressions
- JSR 310: Date and Time API
- JSR 337: Java SE 8 Release Contents
- OpenJDK website
- Java Platform, Standard Edition 8, API Specification
No comments :
Post a Comment