There are already Optional use cases in Nacos, so it's time to be careful with this syntax

preface

Java 8 provides many new features, but many friends don't pay attention to it and still use the old writing method. Recently, I read a lot of the source code of the open source framework and found that many API s of Java 8 have been used frequently.

Taking the Nacos framework as an example, there are already typical Optional use cases, and the scenarios are well grasped. If you don't realize that you need to learn at this time, it may be a little hard to see the source code in the future.

In today's article, we will take the use of Optional in Nacos as a case to explain the use of Optional in depth. There are many old rules, including source code, cases and actual combat.

Optional use in Nacos

In Nacos, there is such an interface ConsistencyService, which is used to define the consistency service. The return type of one of the methods is Optional:

/**
 * Get the error message of the consistency protocol.
 *
 * @return the consistency protocol error message.
 */
Optional<String> getErrorMsg();

If you don't know about Optional, you may be a little confused when you see it here. Let's see how Nacos uses Optional. This is implemented in an implementation class PersistentServiceProcessor of the above interface:

@Override
public Optional<String> getErrorMsg() {
    String errorMsg;
    if (hasLeader && hasError) {
        errorMsg = "The raft peer is in error: " + jRaftErrorMsg;
    } else if (hasLeader && !hasError) {
        errorMsg = null;
    } else if (!hasLeader && hasError) {
        errorMsg = "Could not find leader! And the raft peer is in error: " + jRaftErrorMsg;
    } else {
        errorMsg = "Could not find leader!";
    }
    return Optional.ofNullable(errorMsg);
}

That is, determine the returned errorMsg information according to the two variables of hasLeader and hasError. Finally, encapsulate errorMsg into Optional for return.

Let's see how the method getErrorMsg is called:

String errorMsg;
if (ephemeralConsistencyService.getErrorMsg().isPresent()
        && persistentConsistencyService.getErrorMsg().isPresent()) {
    errorMsg = "'" + ephemeralConsistencyService.getErrorMsg().get() + "' in Distro protocol and '"
            + persistentConsistencyService.getErrorMsg().get() + "' in jRaft protocol";
}

You can see that when using, you only need to call the returned Optional isPresent method to judge whether it exists, and then call its get method to obtain it. At this point, you can recall how to implement it without Optional.

At this point, you may have doubts about the usage. It doesn't matter. Let's start to explain the use, principle and source code of Option step by step.

Optional data structure

We all have a little fear in the face of new things. When we split them like a cow and understand their implementation principle, it will not be so terrible.

Looking at the source code of the Optional class, you can see that it has two member variables:

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>(null);

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;
    // ...
}

The EMPTY variable indicates that if an EMPTY Optional instance is created, it has obviously been initialized during loading. value is used to store the objects that we really use in our business. For example, errorMsg above is stored here.

See here, do you realize that Optional is actually a container! Yes, it's right to understand Optional as a container, and then this container encapsulates the API for non empty judgment and acquisition of storage objects for us.

Seeing here, does it feel that Optional is not so mysterious? Isn't it less scary?

The reason why Java 8 introduces Optional is to solve the ugly writing problem of avoiding null pointer exception when using objects. Similar codes are as follows:

if( user != null){
    Address address = user.getAddress();
    if(address != null){
        String province = address.getProvince();
    }
}

Originally for encapsulation, originally for more elegant code. Isn't that what we aspiring programmers pursue.

How to store objects in the Optional container

Let's put the object into the Optional container.

See that the above EMPTY initialization calls the constructor and passes in null value. Can we also encapsulate the object in this way? It doesn't seem to work. Let's take a look at the construction method of Optional:

private Optional() {
    this.value = null;
}

private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

The two existing construction methods are private. It seems that objects can only be encapsulated through other methods provided by Optional. There are usually the following ways.

empty method

The source code of empty method is as follows:

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

Simple and direct, direct strong conversion of EMPTY objects.

of method

The source code of the method is as follows:

// Returns an {@code Optional} with the specified present non-null value.
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

The comment says that it is to create an Optional value for non null values, rather than null values through the objects in the above construction method Requirenonnull method to check:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

That is to say, if the value is null, the pointer exception will be directly discarded.

ofNullable method

The source code of ofNullable method is as follows:

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

ofNullable creates an option for the specified value. If the specified value is null, an empty option is returned. That is, this method supports null and non null constructions of objects.

Recall: the Optional constructor is private and cannot be called externally; The empty method creates an empty option and the of method creates a non empty option and ofNullable, combining the two. Is it so easy?

At this point, a friend may ask, what is the significance of the of method compared with the ofNullable method? In the process of running, if you don't want to hide NullPointerException, that is, if NULL occurs, you should report it immediately. At this time, use the of function. In addition, it can also be used when it is clear that value will not be null.

Judge whether the object exists

The object has been put into Optional, so do you need to judge whether the stored object is null before obtaining it?

isPresent method

The answer to the above question is: Yes. The corresponding method is isPresent:

public boolean isPresent() {
    return value != null;
}

The implementation is simple and straightforward, which is equivalent to obj= Null judgment is encapsulated. If the object exists, the method returns true; otherwise, it returns false.

isPresent is to judge whether the value value is empty, while ifPresent is to do some operations when the value value is not empty:

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

If the Optional instance has a value, call consumer for it, otherwise it will not be processed. Lambda expressions can be directly passed to this method, making the code more concise and intuitive.

Optional<String> opt = Optional.of("New horizon of procedure");
opt.ifPresent(System.out::println);

Get value

When we judge that there is a value in Optional, we can get it. As used in Nacos, call the get method:

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

Obviously, if the value value is null, the method will throw a NoSuchElementException exception. This is why we should first call isPresent method to determine whether value exists. The design here is slightly contrary to the original intention.

Take a look at the usage example:

String name = null;
Optional<String> opt = Optional.ofNullable(name);
if(opt.isPresent()){
	System.out.println(opt.get());
}

Set (or get) default values

So, is there a solution for the case where the above value is null? We can solve this problem by setting (or obtaining) the default value.

orElse method

orElse method: if there is a value, it will be returned; otherwise, it will return other specified values.

public T orElse(T other) {
    return value != null ? value : other;
}

You can see that it is an enhanced version of the get method. If the value of the get method is null, it will throw an exception directly, while orElse will not. If it is only null, it will return the parameter value you passed in.

Use example:

Optional<Object> o1 = Optional.ofNullable(null);
// Output orElse specified value
System.out.println(o1.orElse("New horizon of procedure"));

orElseGet method

orElseGet: orElseGet is similar to the orElse method, except that the default value is obtained. The orElse method takes the incoming object as the default value. The orElseGet method can accept the implementation of the Supplier interface to generate the default value:

public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

When value is null, orElse directly returns the incoming value, and orElseGet returns the value defined in the Supplier implementation class.

String name = null;
String newName = Optional.ofNullable(name).orElseGet(()->"New horizon of procedure");
System.out.println(newName); // Output: program new horizon

In fact, the above example can be directly optimized as orElse, because the implementation of the Supplier interface still returns the input value directly.

orElseThrow method

Orelsethlow: if there is a value, it will be returned; otherwise, an exception created by the Supplier interface will be thrown.

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

Use example:

Optional<Object> o = Optional.ofNullable(null);
try {
  o.orElseThrow(() -> new Exception("abnormal"));
} catch (Exception e) {
  System.out.println(e.getMessage());
}

After learning the above contents, you have basically mastered 80% of the functions of Optional. At the same time, there are two relatively advanced functions: filtering value and converting value.

Filter method filter value

We can obtain the value in Optional through the method mentioned above, but in some scenarios, we also need to judge whether the obtained value meets the conditions. In case of stupid method, check and judge by yourself after obtaining the value.

Of course, you can also use the filter provided by Optional to filter before taking out:

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

The parameter type of filter method is Predicate type. Lambda expression can be passed to this method as a condition. If the result of the expression is false, an EMPTY Optional object will be returned; otherwise, the filtered Optional object will be returned.

Use example:

Optional<String> opt = Optional.of("New horizon of procedure");
Optional<String> afterFilter = opt.filter(name -> name.length() > 4);
System.out.println(afterFilter.orElse(""));

map method conversion value

Similar to the filter method, when we take the value from Optional, we also carry out one-step conversion, such as changing to uppercase or returning the length. Of course, you can take it out in a stupid way and deal with it.

Here, Optional provides us with a map method, which can be operated before taking out:

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));
    }
}

The parameter type of map method is Function, and the apply method of Function will be called to process the value in Optional. If the value in Optional itself is null, it returns NULL; otherwise, it returns the processed value.

Example:

Optional<String> opt = Optional.of("New horizon of procedure");
Optional<Integer> intOpt = opt.map(String::length);
System.out.println(intOpt.orElse(0));

The method with this similar function to the map method is flatMap:

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

It can be seen that it is very similar to the implementation of the map method. The difference is the input parameter type. The input parameter type accepted by the map function is function <? super T, ? Extensions U >, and the input parameter type of flapMap is function <? super T, Optional<U>>.

Examples of flapMap are as follows:

Optional<String> opt = Optional.of("New horizon of procedure");
Optional<Integer> intOpt = opt.flatMap(name ->Optional.of(name.length()));
System.out.println(intOpt.orElse(0));

Comparing with the example of map, we can see that an optional operation is performed on the result in #of flatMap.

Summary

In this paper, starting from the use of Optional in Nacos, we gradually analyze the source code, principle and use of Optional. Now look back and see if the original example has suddenly opened up?

In fact, it's enough to grasp the essence of learning about option: option is essentially a container of objects. After storing objects in it, it can help us do some non empty judgment, value taking, filtering, conversion and other operations.

Understand the essence. If you are not sure which API to use, just look at the source code. At this point, you can continue to look at the source code happily~

New horizon of procedure
Official account " "New horizons of program", a platform that enables you to improve your soft power and hard technology simultaneously, and provides a large amount of data

About the blogger: the author of the technical book "inside of SpringBoot technology", loves to study technology and write technical dry goods articles.

The official account: "new horizon of procedures", the official account of bloggers, welcome the attention.

Technical exchange: please contact blogger wechat: zhuan2quan

Keywords: Java Nacos optional

Added by rodin on Thu, 17 Feb 2022 10:16:22 +0200