4 things to make your Java 8 code more dynamic

Anytime is a good time to refactor your code.

splash_620

Java 8 has a few new features which should help you write more dynamic code. Of course one of the big features was the addition of a lambda syntax. But what about some of the other features that were added? Here are a couple of things that I tell people to do in order to make their code more dynamic and more functional.

Use lambda’s to abstract your functions

Although this is really self-explanatory, it’s important to remind people to do it. Some of you might be saying, “Hey, I’ve used lambdas in my code!” That’s fantastic, but are you using them as much as you could be? Look at your code — I want you to think, “What if I could pass in this part of the function?”

I always tell people to start looking for duplication of their code. Look for functions doing mostly the same things, see if you can pass in a function to handle the part of the code that is different. While you are factoring out some of these duplicate functions, you should, if its possible to re-use the inner functions that you’re creating. Although it might be awesome to have lots of little lambdas everywhere, if you’re rewriting lambdas, you should consider making it a method and use the method reference notation instead.

Let’s say that there are two functions getCustomers() and getCustomerIds().

public List getCustomers() {
        return Customer.getAll();
}
public List getCustomerIds() {
        List customerIds = new LinkedList

So how could we reduce this into a single method? Let’s use the new functional interface.

public  List getCustomers(Functional transformer) {
        List out = new LinkedList

Which allows us to call:

getCustomers(customer -> customer.getCustomerId());

Or we can use the method reference:

getCustomers(Customer::getCustomerId);

Overall, this should allow you to create much more dynamic and reusable code and reduce your code base. There is no exact formula here, you’re going to experiment with creating functions and abstracting the functionality out as necessary. The more you do it the better you’ll get.

Use Optional instead of NULL

There is some contention here about Optionals. Some people feel that you shouldn’t be using them in lieu of NULL. But let’s think about the reasoning of an Optional versus a NULL. NULL means the absence of a value, generally indicating that a value wasn’t found or available. Returning an Optional gives you the ability to return a value or absence thereof safely. But what does that mean “safely?”

Let’s think about a normal function return such as a Customer object. The function can return two different things, either a Customer object or a NULL. It returns two things because operating on the Customer object is not the same as operating on the NULL. You might say, “Wait, what does that mean?” NULL, in this case, just means there is no Customer object. Again, you may say, “But you can’t call anything on that NULL, right?” And since you don’t know which is there (NULL or the Customer), you must check to see if that Customer is NULL before you can do anything on the Customer object.

Returning an Optional allows us to always have an object coming back. This means that your function return will always be of the same type (an Optional). The Optional has two really awesome properties, first you cannot just start using the object, you must get the object. More specifically you can actually do a test on the object to see if it isPresent() similar to a NULL check. For example, let’s say that you have a method of getCustomerById(Long id) which returns an Optional<Customer>. Let’s see an example of updating a customer record.

Optional customer = getCustomerById(customerId);
if(customer.isPresent()) {
        customer.get().setEnabled(true);
        updateCustomer(customer.get());
}

We can use the ifPresent() method and actually make this even cleaner though.

getCustomerById(customerId).ifPresent(customer -> {
        customer.setEnabled(true);
        updateCustomer(customer.get());
})

You can also use the orElse() methods such as orElseThrow() to protect against an empty Optional.

Customer customer = getCustomerById(customerId).
        orElseThrow(() -> new IllegalArgumentException("Invalid customer id"));

What if you wanted to create the object if it didn’t exist?

Customer customer = getCustomerById(customerId).orElseGet(() -> new Customer());

The second really awesome property is that, although people can attempt to get() an empty Optional, the data type itself forces people to acknowledge that they are doing something without proper error checking. And in the cases above, can actually handle those cases when the Optional is empty as part of the datatype itself.

Stop using for loops and start using streams

Streams are one of the most powerful components in Java 8 besides lambdas. Without Streams, Java would’ve had no list comprehensions using lambdas. If you’re using lambdas but not using Streams, you’re really missing out. In many functional languages, list operations are native to the list structures. In Java, they are considered Streams which means that you’ll need to convert a list to a Stream before you can do list operations.

“Ok Josh, how do I use streams?” you may say. Easy, just call stream() on your list object. “Ha, ha, no. I mean, how can I make use of streams?” you’d reply. Well, think about times that you filter() out parts of a list or map() an object into another type. Let’s assume that we need a function that will return all of the enabled customers id’s? Well, with stream(), we can actually do a filter() for only enabled customers on the stream followed by a map() to convert the Customer object to customer ids. We will then collect() these back into a list with the Collectors.toList() static method. It’s pretty simple actually.

customerList.stream().
        filter(customer -> customer.isEnabled()).
        map(customer -> customer.getCustomerId()).
        collect(Collectors.toList());

You can actually make it even easier by using method references.

customerList.stream().
        filter(Customer::isEnabled).
        map(Customer::getCustomerId).
        collect(Collectors.toList())

This type of method chaining with transformations allows us to concisely express our ideas of how the functionality is working. In this instance, we are filtering out only customers that are enabled and converting Customers into their respective customer ids, then collecting them into a list. Now imagine that you wrote a function that you could then dynamically pick which filter to use — this allows us to simply recreate these functions.

Use the Nashorn scripting engine

You might be saying, “Why would I introduce a scripting engine into my application?” You’re right, some of you might have applications that are very simple and that’s fine. But let’s say that you need an application that has functionality that you want to change, a sorting algorithm or something of that nature. Using a scripting engine can actually help you solve this problem. But why do I bring up the Nashorn engine rather than some other scripting engine such as SpEL or Lua? Primarily because Nashorn is actually built into Java 8 and is a JavaScript engine.

But “Why does it being JavaScript matter?” Well, it’s a fairly well known language today and it’s also very forgiving about types. It being dynamic allows us to write small scripts without worrying about type safety. So now you’re saying “Ok, I see what you’re saying, but how could I actually use this?” Start simple, pick a place where you’ve changed sorting algorithms before and make the comparator call your script engine. You can load the script with a known name and voila you’re using the script engine. From there, you can actually start to think of other places where dynamic functionality might help you.

You can do things like handling specific customer types differently where you can specify a type as a string and the application will load the script based on the type. This can also allow you to add new types with only needing to add new JavaScript files. The biggest complaint here is that you need to start testing your JavaScript files, but these become much smaller tests and allow you to release smaller components instead of the whole application.

Where to go from here

The best advice I can give you is to continue to find more places you can abstract functionality out into lambdas. Continue to get rid of your NULL checks by converting them to Optionals. Convert your for loops into streams. More importantly though, help others to understand how to make their code dynamic. The more you can get others to understand and write dynamic code, the more your code base will flourish.

Remember, anytime is a good time to refactor your code — just make sure that your refactor is providing benefit. There will be sometimes that it doesn’t, and that’s ok. Use that to learn why it didn’t work out. Don’t get frustrated, try again and again. Remember when you started programming and wrote broken applications, and got frustrated? You got past all that, right?


Editor’s note: This post is part of our ongoing exploration into learning how to solve programming problems.

Public domain splash image via Pixabay.

tags: , , , , , ,