preface

The Optional class introduced in Java 8 to solve nullPointerExceptions and tedious NULL checking first appeared in Guava. Java 8 has just become part of the class library.

An introduction to

Optional is a class that encapsulates values that hold values of type T; So Optional is essentially a container.

For example, a Person may or may not have a car, so the car variable inside the Person class should not be declared as car. When the variable exists, the Optional class simply encapsulates the car. If the variable does not exist, an empty Optional object is returned using the option.empty () method. As follows:

But what’s the essential difference between null references and option.empty ()? Semantically, they can be considered the same thing, but in practice they are very different: If you try to dereference a NULL, a NullPointerException must be triggered, but using optional.empty () is a valid object.

Let’s take a look at what Optional provides.

create

When it comes to Optional functionality, let’s first look at creating Optional instances.

Empty Optional

As mentioned earlier, you can create an empty Optional object using the static factory method option. empty:

Optional<Car> option = Optional.empty();
Copy the code

Because empty() itself represents an empty object, calling the get method throws NoSuchElementException.

Non-null Optional

You can also use the static factory method option. of to create an Optional object from a non-null value:

Optional<Car> optional = Optional.of(car);
Copy the code

If car is a NULL, this code throws a NullPointerException immediately, rather than waiting until you try to access the car attribute value and return an error.

Optional that can be null

Finally, using the static factory method option. ofNullable, you can create an Optional object that allows null values:

Optional<Car> optional = Optional.ofNullable(car);
Copy the code

If car is null, the resulting Optional object is empty. We can take a look at how it works:

public static <T> Optional<T> ofNullable(T value) {
	return value == null ? empty() : of(value);
}
Copy the code

Based on how it is implemented, we know that Optional. Empty () is returned when the value passed is null. This allows us to encapsulate values that might be null. For example, if there is an instance of Map

that accesses the key index, a NULL is returned if there is no value associated with the key. Therefore, we can use the optional. ofNullable method to encapsulate the return value:
,>

Optional<Object> value = Optional.ofNullable(map.get("key"));
Object value = map.get("key");
Copy the code

This replaces the potential null bug with an empty Optional object.

operation

Now that we have created the Optional instance, we need to operate on it.

isPresent & get

In the Optional class, the isPresent method checks whether the Optional instance contains a value and returns true if it does, false otherwise. As opposed to the isEmpty method there’s also the get method in the Optional class, which is used to get the value in the Optional instance.

Optional<String> optional = Optional.of("is present");
if (optional.isPresent()) {
	System.out.println("the value is " + optional.get());
}
Copy the code

IsPresent is generally used in combination with GET to avoid nullPointerExceptions:

public boolean isPresent(a) {
	returnvalue ! =null;
}
public T get(a) {
	if (value == null) {
		throw new NoSuchElementException("No value present");
	}
	return value;
}
Copy the code

If isPresent is not used, null pointer exceptions may occur. If isPresent is not used, null pointer exceptions may occur. But this approach is different from if(null! = value) makes no difference, so we should try to avoid this combination.

ifPresent

In addition to isPresent’s concise method, Optional also provides an interface to receive functional arguments ifPresent:

public void ifPresent(Consumer<? super T> action) {
	if(value ! =null) { action.accept(value); }}Copy the code

This method accepts a consumptive function. If the value in the Optional instance is not empty, the Consumer accept method is called to consume the value; if it is empty, no processing is done. The above example can be rewritten using ifPresent:

Optional<String> optional = Optional.of("is present");
optional.isPresent((val) -> System.out.println("the value is " + val));
Copy the code

orElse

We can also read the value in Optional using the orElse method.

public T orElse(T other) {
	returnvalue ! =null ? value : other;
}
Copy the code

This way you can define a default value that will be returned by the method if the Optional variable is null.

String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse("Default");
/** * The following output is displayed: * Default */
Copy the code

orElseGet

If this method is not enough, we can use another method orElseGet:

public T orElseGet(Supplier<? extends T> supplier) {
	returnvalue ! =null ? value : supplier.get();
}
Copy the code

This method differs from orElse in that the method implementing the Supplier interface or a Lambda expression is called to return the default value if the value is not present.

String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse(() -> "Default");
/** * The following output is displayed: * Default */
Copy the code

orElseThrow

The orElseThrow method returns the value if it has a value and throws an exception created by the Supplier if it has no value. Let’s see how it works:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
	if(value ! =null) {
		return value;
	} else {
		throwexceptionSupplier.get(); }}Copy the code

In the orElseThrow principle, a Lambda expression or method is passed to throw an exception if the value does not exist:

class NoValueException extends RuntimeException {
    public NoValueException(a) {
        super(a); }@Override
    public String getMessage(a) {
        return "No value present in the Optional instance"; }}public static Integer orElseThrow(a) {
	return (Integer) Optional.empty().orElseThrow(NoValueException::new);
}
public static void main(String[] args) {
	orElseThrow();
}
* Exception in thread "main" xx.NoValueException: No value present in the Optional instance */
Copy the code

The difference between orElseThrow and orElseGet is that one throws an exception when there is no value, and one uses a Lambda expression to implement a default value when there is no value.

OrElseThrow only throws an exception when there is no value. What about methods that themselves throw an exception?

Now, let’s take integer.parseInt (String) as an example:

public static Integer toInt(String s) {
	try {
		// If the String can be converted to the corresponding Integer, return it wrapped in an Optional object
		return Integer.parseInt(s);
	} catch (NumberFormatException e) {
		return null;	// Return null or throw an exception}}Copy the code

When converting a String to an int, this method throws a NumberFormatException if the corresponding integer cannot be resolved. We catch the exception in this method using a try/catch statement, and cannot use an if condition to control whether a variable’s value is null.

In this case, we can use the Optional class to model invalid values returned by unconverted strings, so we can modify the above method:

public static Optional<Integer> toInt(String s) {
	try {
		// If the String can be converted to the corresponding Integer, return it wrapped in an Optional object
		return Optional.of(Integer.parseInt(s));
	} catch (NumberFormatException e) {
		return Optional.empty();	// Otherwise return an empty Optional object}}Copy the code

This method of returning Optional works for many methods, so we just need to get an instance of the value wrapped by Optional.

map

Optional provides a map method to extract information from an object. It works as follows:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
	Objects.requireNonNull(mapper);
	if(! isPresent())return empty();
	else {
		returnOptional.ofNullable(mapper.apply(value)); }}Copy the code

The map operation applies the provided function to each element of the flow. We can think of an Optional object as a special kind of collection data that contains at most one element. If Optional contains a value, the value is converted using a Lambda expression that implements the Function interface. If you are not familiar with the Function interface, consult this article. The map method is as follows:

class Car {
	private String name;
	privateString type; . Omit getters and setters... } Optional<Car> optional = Optional.ofNullable(car); Optional<String> name = optional.map(Car::getName);Copy the code

flatMap

We can use the map method to get the Car name from the Person class wrapped by the Optional class:

class Person {
	private Optional<Car> car;
	public Person(Car car) {
		this.car = Optional.of(car); }... Omit getters and setters... } Person person =new Person(new Car());
Optional<Person> optPerson = Optional.of(person);
Optional<String> name = optPerson.map(Person::getCar)
	.map(Car::getName);
Copy the code

Unfortunately, this code does not compile. Why is that? OptPerson is a variable of type Optional , and calling the map method should be no problem. But getCar returns an Optional

object, which means that the result of the map operation is an Optional
> object. Therefore, its call to getName is illegal because the outermost Optional object contains the value of another Optional object, which of course does not support the getName method.


So, we use the flatMap method. This method takes as an argument a function whose return value is another stream.

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
	Objects.requireNonNull(mapper);
	if(! isPresent()) {return empty();
	} else {
		@SuppressWarnings("unchecked")
		Optional<U> r = (Optional<U>) mapper.apply(value);
		returnObjects.requireNonNull(r); }}Copy the code

Rewrite the above example using flatMap with reference to the map function:

Optional<String> name = optPerson.flatMap(Person::getCar).map(Car::getName);
Copy the code

filter

Sometimes we need to filter the Optional value to get the result we want. We can use the filter method:

public Optional<T> filter(Predicate<? super T> predicate) {
	Objects.requireNonNull(predicate);
	if(! isPresent())return this;
	else
		return predicate.test(value) ? this : empty();
}
Copy the code

This method accepts the Predicate Predicate as an argument. If the value of the Optional object exists and the predicate condition is true, the filter method does nothing and returns the value. Otherwise, the value is filtered out and an empty Optional object is returned.

Optional<String> optionalS = Optional.of("13846901234");
optionalS = optionalS.filter(s -> s.contains("138"));
/** * The above 'filter' method can return the same Optional, otherwise return empty Optional */
Copy the code

conclusion

The introduction of Java.util.Optional

in Java 8 lets us handle null functionally, preventing null pointer exceptions; And supports multiple ways to manipulate values, such as: Map, flatMap, and filter, which can discard nested if-else code blocks, design better apis, and greatly improve code readability. However, if Optional is used in the domain model, it cannot be instantiated because the Serializable interface is not implemented. Nor can it be a field of a class.


For more information, please pay attention to the public account “Hai Ren Ji” and reply to “Resources” to get free learning resources!