Master six operations of Java 8 Optional

Hello, I'm looking at the mountain.

Java 8 introduces a particularly interesting class: Optional, a tool that makes it easier for us to avoid NPE (null pointer exception).

A long time ago, in order to avoid NPE, we wrote a lot of code similar to if (obj! = null) {}. Sometimes if we forget to write, NPE may occur and cause online failure. In the Java technology stack, if someone's code has NPE, it is likely to be laughed at. This exception is considered a low-level error by many people. The emergence of Optional can make it easier for everyone to avoid the probability of being ridiculed because of low-level errors.

Define sample data

First define the object to be operated, the universal Student class and Clazz class (lombok and guava are used):

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Clazz {
    private String id;
    private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String name;
    private Clazz clazz;
}

Then define a set of test data:

final Clazz clazz1 = new Clazz("1", "Class 11 of senior high school");

final Student s1 = new Student("1", "Zhang San", clazz1);
final Student s2 = new Student("2", "Li Si", null);

final List<Student> students = Lists.newArrayList(s1, s2);
final List<Student> emptyStudents = Lists.newArrayList();
final List<Student> nullStudents = null;

Create instance: of, ofNullable

In order to control how instances are generated, and to tighten the definition of null value Optional, Optional defines the constructor as private. To create an Optional instance, you can use the of and ofNullable methods.

The difference between the two methods is that the parameters passed in are null and cannot be thrown. Therefore, for possible null results, be sure to use of nullable.

The code is as follows:

Optional.of(students);
Optional.of(emptyStudents);
Optional.ofNullable(nullStudents);

There is also a static method in the Optional class: empty, which directly returns an internally defined constant Optional <? > Empty = new Optional < > (), the value of this constant is null. ofNullable method also implements null wrapping with the help of empty:

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

Therefore, the null optional wrapper class points to the same instance object, optional empty() == Optional. Ofnullable (null) returns true. In other words, an empty option is a singleton.

For convenience of description, options with null values are collectively referred to as "null options" in the following.

Get data: get

The Optional get method is a bit tricky. Let's take a look at its source code:

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

That is, when the Optional value is null, the get method will throw a NoSuchElementException exception. If you don't want to throw an exception, you can either be 100% sure it's not empty Optional, or use the isPresent method to judge.

If you can be 100% sure that it is not empty, there is no need to use Optional packaging and return it directly. If the isPresent method needs to be used, it is no different from direct air judgment. Therefore, both the first case and the second case violate the original intention of designing this class.

If the value is empty, judge: isPresent, ifPresent

isPresent is used to judge whether the value is empty, similar to obj= Null, ifPresent can pass in a Consumer operation. When the value is not empty, the Consumer function will be executed. For example:

final Optional<List<Student>> nullValue = Optional.ofNullable(nullStudents);

if (nullValue.isPresent()) {
    System.out.println("value: " + nullValue.get());
}

The above method is equivalent to:

nullValue.ifPresent(value -> System.out.println("value: " + value));

isPresent judges whether the writing method is very familiar. The feeling can be directly written as:

if (nullStudents != null) {
    System.out.println("value: " + nullStudents);
}

For isPresent, if it is within its controllable code range, there is no need to encapsulate the value and then empty it. For the code beyond your control, the subsequent filter or map method may be better than isPresent.

For ifPresent, there are some restrictions when using it, that is, when it must be non empty Optional, the incoming Consumer function will be executed.

Value processing: map, flatMap

Map and flatMap are methods to operate on the value of Optional. The difference is that map will wrap the result into Optional and return it. flatMap will not. However, the return values of both methods are of Optional type, which requires that the return value of the method function of flatMap should be of Optional type.

Let's take a look at the implementation of map:

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

You can see that if the value of Optional is null, the map directly returns Optional Empty, otherwise the function result will be executed and Optional Ofnullable wrap and return. In other words, as long as the class structure allows, we can continue to map, just like grilling onions, layer by layer, until the core.

For example, we need to get the class name of s2. When defining, we define the clazz attribute of s2 as null. If it needs to be written as:

String clazzNameOld;
if (s2 != null && s2.getClazz() != null && s2.getClazz().getName() != null) {
    clazzNameOld = s2.getClazz().getName();
} else {
    clazzNameOld = "DEFAULT_NAME";
}

Now with the help of Optional, it can be written as:

final String clazzName = Optional.ofNullable(s2)
        .map(Student::getClazz)
        .map(Clazz::getName)
        .orElse("DEFAULT_NAME");

The code doesn't seem to have changed much, but if there are class objects inside Clazz. Or should we write one less layer of check when judging if? Moreover, the subtlety of map lies in that its return value is always Optional. In this way, we can call the map method repeatedly without being interrupted in the middle and add various empty judgment logic.

Processing with empty value: orElse, orElseGet, orElseThrow

These methods can be combined with map operation to complete object operation. When the value is null, orElse and orElseGet return the default value, and orelsethlow throws the specified exception.

The difference between orElse and orElseGet is that the parameter passed in by orElse method is an explicit default value, and the parameter passed in by orElseGet method is a function to obtain the default value. If the construction process of default value is complex and requires a series of operation logic, orElseGet must be used, because orElseGet will execute the function and return the default value when the value is empty. If the value is not empty, the function will not be executed. Compared with orElse, it reduces one process of constructing default value.

Also take the above example:

orElse:

final String clazzName = Optional.ofNullable(s2)
        .map(Student::getClazz)
        .map(Clazz::getName)
        .orElse(null);

orElseGet is written as follows:

final String clazzName = Optional.of(s2)
        .map(Student::getClazz)
        .map(Clazz::getName)
        .orElseGet(() -> null);

If the clazz property must not be empty, an exception will be returned. You can use orElseThrow:

final String clazzName = Optional.of(s2)
        .map(Student::getClazz)
        .map(Clazz::getName)
        .orElseThrow(() -> new IllegalArgumentException("clazz Illegal attribute"));

Conditional filter: filter

The filter method provides value verification. If the value verification is true, the current value is returned; Otherwise, null Optional is returned. For example, we need to traverse students, find the class attribute that is empty, and print the student id:

for (final Student s : students) {
    Optional.of(s)
            .filter(x -> x.getClazz() == null)
            .ifPresent(x -> System.out.println(x.getId()));
}

Others: equals, hashCode, toString

Optional overrides these three methods. Because optional can be considered as a wrapper class, we should rewrite these three methods around the wrapped value. The source code of these three methods is given below:

public boolean equals(Object obj) {
    // Same object judgment
    if (this == obj) {
        return true;
    }

    // Type judgment
    if (!(obj instanceof Optional)) {
        return false;
    }

    Optional<?> other = (Optional<?>) obj;
    // Finally, it is the judgment of value
    return Objects.equals(value, other.value);
}

public int hashCode() {
    // hashCode of direct return value
    return Objects.hashCode(value);
}

public String toString() {
    return value != null
        ? String.format("Optional[%s]", value) // The value of toString is used
        : "Optional.empty";
}

Equals method, optional of(s1). Equals (optional. Of (S2)) is completely equivalent to S1 equals(s2).

The hashCode method directly returns the hashCode of the value. If it is empty Optional, it returns 0.

ToString method. In order to identify that it is Optional, wrap the print data. If it is empty, the string "Optional.empty" will be returned; If it is not empty, the return is "Optional [toString of value]".

Conclusion

The reason why NPE is annoying is that as long as there is NPE, we can solve it. However, once it occurs, it is an afterthought, and online faults may have occurred. But in the Java language, NPE is easy to appear. Optional provides a template method to effectively and efficiently avoid NPE.

Next, we summarize the above use:

  1. Optional is a wrapper class and is immutable and non serializable
  2. There is no public constructor. of and ofNullable methods need to be used for creation
  3. Empty Optional is a singleton, which refers to Optional EMPTY
  4. To get the value of Optional, you can use get, orElse, orElseGet, or else throw

In addition, there are some practical suggestions:

  1. Before using the get method, you must check with isPresent. However, before using isPresent, first think about whether you can use orElse, orElseGet and other methods to replace the implementation.
  2. orElse and orElseGet. orElseGet is preferred. This is inert computing
  3. Optional do not use it as a parameter or class attribute, but as a return value
  4. Try to extract the function parameters of map and filter as separate methods, so as to maintain the chain call

Recommended reading

Hello, I am looking at the mountain, official account: looking at the mountain cottage, the 10 year old ape, the contributor of the open source. Swim in the code world and enjoy life.

Personal homepage: https://www.howardliu.cn
Personal blog: Master six operations of Java 8 Optional
CSDN home page: http://blog.csdn.net/liuxinghao
CSDN blog: Master six operations of Java 8 Optional

Keywords: Java optional

Added by NeoSsjin on Wed, 09 Feb 2022 06:49:40 +0200