Thursday, 14 January 2016

Optional class in Java 8

Optional class, added in Java 8, provides another way to handle situations when value may or may not be present. Till now you would be using null to indicate that no value is present but it may lead to problems related to null references. This new class java.util.Optional introduced in Java 8 can alleviate some of these problems.

General structure of Optional class

Class Optional<T>

Here T is the type of the value stored in the Optional instance. Note that Optional instance may contain value or it may be empty, but if it contains value then it is of type T.

Let's see an example how null checks may end up being the bulk of our logic. Let's say we have a class Hospital, which has a cancer ward and cancer ward has patients.

So if we need id of a patient following code will do just fine.

String id = hospital.getCancerWard().getPatient().getId();

But many hospitals don't have a separate cancer ward, what if null reference is returned to indicate that there is no cancer ward. That would mean getPatient() will be called on a null reference, which will result in NullPointerException at runtime.

In this we'll have to add null checks to avoid null pointer exception.

if(hospital != null){
    Ward cancerWard = hospital.getCancerWard();
    if(cancerWard != null){
        Patient patient = cancerWard.getPatient();
        if(patient != null){
            String id = patient.getId();
        }
    }
}

Now let us see how Optional can help in this case and in many other cases -

How to create Optional objects

Optional doesn't have any constructors, there are several static methods for the purpose of creating Optional objects.

If you want to create an Optional instance which doesn't have any value, you can use empty method.

As Exp.

Optional<String> op = Optional.empty();

If you want to create an Optional instance with a given value, you can use of method.

Optional<String> op = Optional.of("Hello");

Or if you want to create instance of Optional with Ward class (as mentioned above) object

Ward ward = new Ward();
Optional<Ward> op = Optional.of(ward);

Note that, in this case if Ward is null, NullPointerException will be thrown immediately.

There is also ofNullable method which returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.

How to use Optional Values

Usage of Optional is more appropriate in the cases where you need some default action to be taken if there is no value.

Suppose for class Patient, if patient is not null then you return the id otherwise return the default id as 9999.
This can be written typically as -

String id = patient != null ? patient.getId() : "9999";

Using an Optional object same thing can be written using orElse() method which returns the value if present, otherwise returns the default value.

Optional<String> op1 = Optional.ofNullable(patient.getId());
String id = op1.orElse("9999");

You can use orElseGet() which will execute the given logic to get the default.

As exp. You may way want to get System property if not already there

String country = op.orElseGet(()-> System.getProperty("user.country"));

Note that lambda expression is used here to implement a functional interface.

You can use orElseThrow() method if you want to throw an exception if there is no value.

op1.orElseThrow(IllegalStateException::new);

Note that here double colon operator (method reference) is used to create exception.

There are scenarios when you want to execute some logic only if some value is present or do nothing. ifPresent() method can be used in such scenarios.

As exp. You want to add value to the list if there is value.

op1.ifPresent(v->numList.add(v));

How not to use Optional Value

There is a get() method provided by Optional class which returns the value, if a value is present in this Optional, otherwise throws NoSuchElementException. Using it directly without first ascertaining whether value is there or not is not safer than normally using value without checking for null.

Optional<Hospital> op = Optional.of(hospital);
op.get().getCancerWard();

Second wrong usage is using both isPresent and get method which is as good as null checks.

if (op.isPresent()){
    op.get().getCancerWard();
}

Better way would be to use map or flatMap.

Using flatMap

If we get back to the classes mentioned in the beginning; Hospital, Ward and Patient then if we use Optional the classes will look like this -

public class Hospital {
    private Optional<Ward> cancerWard;

    public Optional<Ward> getCancerWard() {
        return cancerWard;
    }

    public void setCancerWard(Optional<Ward> cancerWard) {
        this.cancerWard = cancerWard;
    }

}
public class Ward {
    private Optional<Patient> patient;

    public Optional<Patient> getPatient() {
        return patient;
    }

    public void setPatient(Optional<Patient> patient) {
        this.patient = patient;
    }   
}
public class Patient {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

So now if we have to write this

 
String id = hospital.getCancerWard().getPatient().getId();

With Optional then we have to use flatMap() method. There is a map() method too but writing something like this

 
String id = op.map(Hospital::getCancerWard).map(Ward::getPatient).map(Patient::getId).orElse("9999");

Will result in compiler error as the return type of map is Optional<U> and the return type of getCancerWard() is Optional<Ward> so it will make the result of the map of type Optional<Optional<ward>>. That is why you will get compiler error with map method in this case.

Using flatMap will apply the provided Optional-bearing mapping function to it i.e. flatten the 2 level Optional into one.

Thus the correct usage in this chaining is

 
String id = op.flatMap(Hospital::getCancerWard).flatMap(Ward::getPatient).map(Patient::getId).orElse("9999");

That's all for this topic Optional class in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!

Related Topics

  1. interface default methods in Java 8
  2. interface static methods in Java 8
  3. Functional interface annotation in Java 8
  4. Functional interfaces & lambda expression
  5. Lambda expression examples in Java 8
  6. Method reference in Java 8

You may also like -

>>>Go to Java advance topics page

No comments:

Post a Comment