Java 8 Optional


Bhaskar S 10/17/2014


The java.util.Optional<T> container object is a wrapper around an object, which may or may not contain a non-null value.

So why do we need Optional ???

Java developers often times encounter the dreaded NullPointerException. This happens when one accesses a reference object whose value ends up being null.

In Java, a null value is used to indicate absence of a value. In contrast, in functional programming languages (like Haskell or Scala), a function call always returns a special value, which allows one to get the actual return value (if there is one) or can indicate the absence of a value. This is much more elegant and safer.

Let us jump into an example to understand why we need Optional.

In this hypothetical example, we will search a catalog for a product and display the product name and its price.

The following is a simple class for product:

Listing.1
/*
 * 
 * Name:   Product
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

public class Product {
    private String name;
    private double price;
    
    public Product() {
    }
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }
}

The following is a simple catalog application that is finding two products - A and C and displaying their information:

Listing.2
/*
 * 
 * Name:   Catalog
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;

public class Catalog {
    static List<Product> products = new ArrayList<Product>();
    
    public static void main(String[] args) {
        try {
            initialize();
            
            Product pa = findProduct("A");
            System.out.printf("Name: %s, Price: %.2f\n", pa.getName(), pa.getPrice());
            
            Product pc = findProduct("C");
            System.out.printf("Name: %s, Price: %.2f\n", pc.getName(), pc.getPrice());
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
    }
    
    public static void initialize() {
        Product p1 = new Product("A", 1.25);
        Product p2 = new Product("B", 2.50);
        Product p3 = new Product();
        
        products.add(p1);
        products.add(p2);
        products.add(p3);
    }
    
    public static Product findProduct(String name) {
        Product prod = null;
        
        for (Product p : products) {
            if (p.getName().equals(name)) {
                prod = p;
                break;
            }
        }
        
        return prod;
    }
}

Executing the program from Listing.2 will generate the following output:

Output.1

Name: A, Price: 1.25
java.lang.NullPointerException
	at com.polarsparc.java8.Catalog.findProduct(Catalog.java:48)
	at com.polarsparc.java8.Catalog.main(Catalog.java:26)

In Listing.2, we did not check for nulls and hence this exception.

The following is the safer version of Listing.2 using defensive coding practices:

Listing.3
/*
 * 
 * Name:   Catalog2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;

public class Catalog2 {
    static List<Product> products = new ArrayList<Product>();
    
    public static void main(String[] args) {
        try {
            initialize();
            
            Product pa = findProduct("A");
            if (pa != null) {
                if (pa.getName() != null) {
                    System.out.printf("Name: %s, Price: %.2f\n", pa.getName(), pa.getPrice());
                }
            }
            
            Product pc = findProduct("C");
            if (pc != null) {
                if (pc.getName() != null) {
                    System.out.printf("Name: %s, Price: %.2f\n", pa.getName(), pa.getPrice());
                }
            }
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
    }
    
    public static void initialize() {
        Product p1 = new Product("A", 1.25);
        Product p2 = new Product("B", 2.50);
        Product p3 = new Product();
        
        products.add(p1);
        products.add(p2);
        products.add(p3);
    }
    
    public static Product findProduct(String name) {
        Product prod = null;
        
        for (Product p : products) {
            if (p.getName() != null && p.getName().equals(name)) {
                prod = p;
                break;
            }
        }
        
        return prod;
    }
}

Executing the program from Listing.3 will generate the following output:

Output.2

Name: A, Price: 1.25

As can be observed from Listing.3, even for this very basic application, one needs to check every reference for nulls.

The following simple test program illustrates the basic usage of Optional:

Listing.4
/*
 * 
 * Name:   OptionalTest
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

import java.util.Optional;

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> op1 = Optional.empty();
        
        try {
            System.out.println(op1.get());
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
        
        String s2 = null;
        
        try {
            Optional.of(s2);
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
        
        Optional<String> op2 = Optional.ofNullable(s2);
        if (op2.isPresent()) {
            System.out.println(op2.get());
        }
        
        String s3 = "Optional";
        Optional<String> op3 = Optional.ofNullable(s3);
        if (op3.isPresent()) {
            System.out.println(op3.get());
        }
        
        op3.ifPresent(s -> System.out.println("Java 8 " + s));
    }
}

Executing the program from Listing.4 will generate the following output:

Output.3

java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at com.polarsparc.java8.OptionalTest.main(OptionalTest.java:20)
java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Objects.java:203)
	at java.util.Optional.<init>(Optional.java:96)
	at java.util.Optional.of(Optional.java:108)
	at com.polarsparc.java8.OptionalTest.main(OptionalTest.java:29)
Optional
Java 8 Optional

The code in Listing.4 needs a little explanation.

!!! ATTENTION !!!

Trying to access the value of an un-initialized empty Optional with get() will result in the following exception:

java.util.NoSuchElementException: No value present


!!! ATTENTION !!!

Trying to create an Optional instance with of() on a null value will result in the following exception:

java.lang.NullPointerException

Now that we have briefly explored Optional, the following is the refactored simple class for product:

Listing.5
/*
 * 
 * Name:   Product2
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

import java.util.Optional;

public class Product2 {
    private String name;
    private double price;
    
    public Product2() {
    }
    
    public Product2(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }
}

The following is the more elegant version of Listing.3 using Optional:

Listing.6
/*
 * 
 * Name:   Catalog3
 * 
 * Author: Bhaskar S
 * 
 * Date:   10/18/2014
 * 
 */

package com.polarsparc.java8;

import java.util.List;
import java.util.ArrayList;
import java.util.Optional;

public class Catalog3 {
    static List<Product2> products = new ArrayList<Product2>();
    
    public static void main(String[] args) {
        try {
            initialize();
            
            Optional<Product2> pa = findProduct2("A");
            pa.ifPresent(p -> {
                p.getName().ifPresent(n -> System.out.printf("Name: %s, Price: %.2f\n", n, p.getPrice()));
            });
            
            Optional<Product2> pc = findProduct2("C");
            pc.ifPresent(p -> {
                p.getName().ifPresent(n -> System.out.printf("Name: %s, Price: %.2f\n", n, p.getPrice()));
            });
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
    }
    
    public static void initialize() {
        Product2 p1 = new Product2("A", 1.25);
        Product2 p2 = new Product2("B", 2.50);
        Product2 p3 = new Product2();
        
        products.add(p1);
        products.add(p2);
        products.add(p3);
    }
    
    public static Optional<Product2> findProduct2(String name) {
        Product2 prod = null;
        
        try {
            for (Product2 p : products) {
                if (p.getName().isPresent() && p.getName().get().equals(name)) {
                    prod = p;
                    break;
                }
            }
        }
        catch (Throwable ex) {
            ex.printStackTrace(System.err);
        }
        
        return Optional.ofNullable(prod);
    }
}