Page 1 of 1

Avoiding NullPointerExceptions by using Optional<T> Rate Topic: ***** 2 Votes

#1 ndc85430  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 587
  • View blog
  • Posts: 2,468
  • Joined: 13-June 14

Posted 28 October 2017 - 02:02 AM

Avoiding NullPointerExceptions by using Optional<T>

If you've programmed in Java for any reasonable amount of time, you've probably had experience with null values and that if you forget to check for null, your program can end up throwing a NullPointerException.

Optional<T> is a generic class that solves this problem. It represents the possibility of having a value of type T, or not. If it's helpful, you can think of Optional<T> as being a collection (like List<T>) that is either empty or contains a single value. Ideas like this have been borrowed from functional programming languages like Scala and we'll touch upon some others in this tutorial.

Optional<T> and the other functional features used in this tutorial were introduced in Java 8, so make sure that your version of Java is up to date.

For the purpose of this example, let's assume we have a class Book that looks like

public class Book {
    public final String isbn;
    public final String title;
    public final String author;
    public final String coverImagePath;

    public Book(String isbn, String title, String author, String coverImagePath) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.coverImagePath = coverImagePath;
    }
}


and a BookDatabase whose interface allows us to retrieve a Book by its ISBN:

public interface BookDatabase {
    Book getBook(String isbn);
}



Let's assume that implementations of this interface will return null when a Book with the given ISBN can't be found. Note that I'm not showing an implementation as it isn't really important - we just care about getting a Book or null and where these books are stored (whether in memory, in a file, or some database) is irrelevant.

Typical Java code might look like this:

BookDatabase db = ...;

Book book = db.getBook("978-0-12-345678-0");
System.out.println(book.title);



If book is null, then trying to access the title on line 4 will throw a NullPointerException. Of course, the usual way to fix this is to explicitly check for null:

if (book != null) {
   System.out.println(book.title);
}


Let's see how using Optional relieves us of having to remember to check for the absence of a value. We'll assume that BookDatabase's getBook now returns an Optional<Book> instead:

import java.util.Optional;

public interface BookDatabase {
    Optional<Book> getBook(String isbn);
}


Of course the calling code would have to change:

Optional<Book> maybeBook = db.getBook("978-0-12-111111-7");



I have chosen the name maybeBook to express clearly the fact that we have the possibility of not having a Book. Now, since we said that Optional is like a collection, we need to get the value out of it to use it. We may say that Optional wraps a value of type T.

Let's say that as in the original code using null, we want to print the Book's title, if we have a Book. Optional has a method ifPresent that lets us do something when we have a value. If we don't, ifPresent simply does nothing. The way we express what we want to do is to pass ifPresent a function that takes the Book as an argument and does what we want with it:

maybeBook.ifPresent(book -> System.out.println(book.title));



Some new syntax is introduced here. The argument passed to ifPresent is called a lambda expression or anonymous function (i.e. a function without a name). On the left hand side of the arrow is the function's parameter list and on the right hand side is the function's body. That is, the lambda expression

book -> System.out.println(book.title)



is equivalent to a function defined in the normal way, e.g.

public void printBookTitle(Book book) {
    System.out.println(book.title);
}



just without a name.

If maybeBook did contain a Book, then the anonymous function would be called by ifPresent and the Book's title would be printed. Crucially, if maybeBook did not contain a Book, ifPresent would do nothing. Therefore, this is safer than using null because we now don't need to worry about the absence of a value - no NullPointerException will be thrown.

In functional programming, it is quite common to pass functions to other functions. When a function takes another function as an argument, we say it is a higher order function. So, ifPresent is an example of a higher order function.

Printing to the screen is one example of a side effect. Side effects are things that cause a change in state somewhere. Other examples might be writing to a database or sending data over the network. Functions that perform only side effects typically do not have a value to return for use in subsequent computation (hence the return type of System.out.println is void). ifPresent is used in cases where we want to unwrap the value inside the Optional and perform a side effect.

Instead of performing a side effect, we may want to transform a value in some way to be used later. Let's say that we want to get the title from our Book and turn it into uppercase. When using the original null version, we'd have

if (book != null) {
    String uppercaseTitle = book.title.toUpperCase();

    // Do something with uppercaseTitle
}


With Optional, we use the map function to transform the value. map is a higher order function, taking a function that describes how to transform the value:

Optional<String> maybeUppercaseTitle = maybeBook.map(book -> book.title.toUpperCase());



Note here that map returns an Optional wrapping the value returned by the function passed to it. That is, since

book.title.toUpperCase()



returns a String,

maybeBook.map(book -> book.title.toUpperCase())


returns Optional<String>. When maybeBook does not contain a Book, map will simply return an empty Optional<String>.

Let's consider one last situation. Some books have cover images and others do not. With the implementation of Book given at the beginning of this tutorial, for those without cover images, we might simply have a null value for the coverImagePath field. To deal with that possibility, we'd need two null checks:

if (book != null) {
    if (book.coverImagePath != null) {
        // Do something with book.coverImagePath
    }
}


There's more to go wrong, not to mention that these nested if statements are not particularly pleasant to read (and removing the nesting in favour of a single if with an and isn't much better).

You know where this is going: we make use of Optional, so that the definition of Book changes to

import java.util.Optional;

public class Book {
    public final String isbn;
    public final String title;
    public final String author;
    public final Optional<String> coverImagePath;

    public Book(String isbn, String title, String author, Optional<String> coverImagePath) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.coverImagePath = coverImagePath;
    }
}


So, we have an Optional<Book> as before

Optional<Book> maybeBook = db.getBook("978-0-12-111111-7");


and want to get the cover image path, if there is one. One might think that map is appropriate here, so that we could write

maybeBook.map(book -> book.coverImagePath)


The problem, though, is that since book.coverImagePath has type Optional<String>, map will return an Optional<Optional<String>> since it wraps the value that its function returns in an Optional, i.e

Optional<Optional<String>> maybeCoverImagePath = maybeBook.map(book -> book.coverImagePath);



This is somewhat annoying, because we now have to unwrap two Optionals. For cases where we have nested Optionals like this and we want to transform the value and get rid of the nesting, we use flatMap:

Optional<String> maybeCoverImagePath = maybeBook.flatMap(book -> book.coverImagePath);



I highly recommend looking at the following resources:

API documentation for Optional<T>
Lambda expressions
Aggregate operations, to see more examples of higher order functions

This post has been edited by ndc85430: 04 November 2017 - 06:26 AM


Is This A Good Question/Topic? 0
  • +

Replies To: Avoiding NullPointerExceptions by using Optional<T>

#2 ndc85430  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 587
  • View blog
  • Posts: 2,468
  • Joined: 13-June 14

Posted 05 November 2017 - 11:00 AM

One could ask why we simply don't return, say, a List<T> instead of using Optional<T>. I think there are two issues. First, you'd have to check for an empty list, or risk an IndexOutOfBoundsException. Of course, one could avoid this by simply iterating over the list (or use Java 8 features: convert it to a Stream<T> and use map or forEach as appropriate), but the problem is then one of semantics. Having a list means having 0 or more items and just isn't as expressive as we can be - the case of having 0 or 1 item is common enough that it warrants its own type and that's what Optional<T> gives you.

It would also be useful to get feedback on this tutorial. Please ask questions if anything was especially unclear.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1