The latest expert opinions, articles, and guides for the Java professional.
Hi there, do you like trains? Here at RebelLabs, we love trains! Not just a regular love, but an unconditional love as well as immense respect for trains of all kinds. Let me give you an example, a Java train that is regularly delayed till a later date.
Long story short, Java 9 should have been feature complete by now, but it isn’t. There’s no need to assign blame or to point fingers and complain how bad the red shepherd of Java is. Jigsaw is a hard problem and takes time to get right and… we’re not going to talk about that right now. Java 9 is far away from being just around the corner, so we’ll have be friends with Java 8 for a little while longer.
Java 8 has already been with us for quite some time. Let’s reflect on it and talk about the best practices that have naturally grown in its typical usage.
In this post we’ll look at the hot topics of the Java 8 language: default methods, lambdas and streams and using Optional to represent absent values.
Default Methods in Interfaces
The ability to specify default method implementations in interfaces was added into JDK 8 so that collections could evolve without breaking backward compatibility. Previously, we couldn’t just add a method to an interface without requiring all the implementing subclasses to specify an implementation of the new method. Breaking backward compatibility is a deal-breaker for Java. So since version 1.8 we can mark a method with the default keyword and provide the body of the method right in the interface. This, as any powerful feature does, opens all sorts of doors for unmaintainable, confusing code, if abused. However in small doses one can enhance existing interfaces and make them more useful in the codebase.
The main rule of thumb for using default methods is not to abuse them and not to make the code messier than it would be without it. For example, if you want to add some sort of functionality to Java classes without polluting their hierarchy with a common superclass, consider creating a separate interface just for this one utility method. Here’s an example of an interface called Debuggable that uses the reflection API to get the access to the object’s fields and provides a decent toString() implementation for the object that prints the fields values.
The important part here is the default keyword on the method signature. One could now use this for any class that you need to peek at, like this:
Which prints the expected line: “
Main [ a = 100 b = Home ]”.
Functional interfaces of Java 8 deserve a special mention here. A functional interface is one that declares a single abstract method. This method will be called if we use it with the lambda syntax later. Note that default methods don’t break single abstract method contract. You can have a functional interface bearing many default methods if you choose. Don’t overuse it though. Code conciseness is important, but code readability trumps it by far.
Lambdas in Streams
Lambdas, oh sweet lambdas! Java developers have been eagerly waiting for you. For years Java has received the label of not being an appropriate programming language for functional programming techniques, because functions were not the first class citizens in the language. Indeed, there wasn’t a neat and accepted way to refer to a code block by a name and pass it around. Lambdas in JDK 8 changed that. Now we can use method references, for better or worse, to refer to a specific method, assign the functions into variables, pass them around, compose them and enjoy all the perks the functional programming paradigm offers.
The basics are simple enough, you can define functions using the arrow (->) notation and assign them to fields. To make things simpler when passing functions as parameters, we can use a functional interface, with only one abstract method.
There are a bunch of interfaces in the JDK that are created for almost any case: void functions, no parameters functions, normal functions that have both parameters and the return values. Here’s a taste of how your code might look using the lambda syntax.
The caveat here is that the code is tough to manage if you let your anonymous functions grow over a certain threshold. Think about the fattest lambda you’ve seen? Right. That should have never existed.
The most natural place for a lambda to exist is code which processes data. The code specifies the data flow and you just plug in the specific functionality that you want to run into the framework. The Stream API immediately comes to mind. Here’s an example:
That’s pretty self-explanatory, right?
In general, when working with streams, you transform the values contained in the stream with the functions you provide for example using the lambda syntax. However, for a better explanation, check out the Java 8 Streams cheat sheet, it has a short, clear explanation when and why you want to use certain stream methods and what pitfalls might await you.
- If the code doesn’t specify the framework for the data flow into which you plug your functions, consider avoiding multiplying lambdas. A proper class might be more readable.
- If your lambda grows above 3 lines of code – split it: either into several map() invocations that process the data in steps or extract a method and use the method reference syntax to refer to it.
- Don’t assign lambdas and functions to the fields of the objects. Lambdas represent functions and those are best served pure.
Optional is a new type in Java 8 that wraps either a value or null, to represent the absence of a value. The main benefit is that it your code can now gracefully handle the existence of null values, as you don’t have to explicitly check for nulls anymore.
Optional is a monadic type you can map functions into, that will transform the value inside the Optional. Here’s a simple example, imagine you have an API call that might return a value or null, which you need to then process with the transform() method call. We’ll compare code with and without using Optional types.
It isn’t nice to have null checks polluting your code, is it? The best part is that we can now live inside this Optional world and never leave it, since all functions can be mapped into it. What about the functions that already return an Optional? Have no fear with the flatMap method, you won’t end up double wrapping Optionals. Check out flatMap’s signature:
It takes care of the required unwrapping so you have just one level of Optionals!
Now, before you rewrite all your code to have Optionals all over the place. Hold on for a minute longer. Here’s a rule of thumb for where you want to use Optional types:
- Instance fields – use plain values. Optional wasn’t created for usage in fields. It’s not serializable and adds a wrapping overhead that you don’t need. Instead use them on the method when you process the data from the fields.
- Method parameters – use plain values. Dealing with tons of Optionals pollutes the method signatures and makes the code harder to read and maintain. All functions can be lifted to operate on optional types where needed. Keep the code simple!
- Method return values – consider using Optional. Instead of returning nulls, the Optional type might be better. Whoever uses the code will be forced to handle the null case and it makes for cleaner code.
All in all, using the Optional type correctly helps you keep your codebase clean and readable. And that’s very important! Disregarding that, both in life and in your code, is a recipe for disaster!
I hope this post gives you an idea of some of the best practices on using Java 8 features and still have readable and maintainable code. If you liked it, don’t forget to print the handy 1 page cheat sheet we prepared with the takeaways from the post so you can print it out and put it under your less experienced colleague’s coffee cup.
What are your rules of thumb and best practices with dealing with Java 8 features? Hit the comment section below or find us on Twitter: @ZeroTurnaround.
This is our first one page cheat sheet. Are there any other areas you’d like a cheat sheet on? Would you like to read a post summarizing best practices of Java Date & Time API, more on stream processing or how to use Collectors to the fullest?
Make your selection on this single question survey and we’ll try our best to share our experience with you.
Leave a comment