理解、学习与使用 Java 中的 Optional 已翻译 100%

oschina 投递于 2017/09/21 18:48 (共 16 段, 翻译完成于 09-22)
阅读 10854
收藏 112
8
加载中

One of the most interesting features that Java 8 introduced to the language was the Optional class. The main issue this class is intended to tackle is the infamous NullPointerException that every Java programmer knows only too well.

Essentially, this is a wrapper class that contains an optional value, meaning it can either contain an object or it can simply be empty.

Optional comes along with a strong move towards functional programming in Java and is meant to help in that paradigm, but definitely also outside of that.

已有 2 人翻译此段
我来翻译

Let’s start with a simple use-case. Before Java 8, any number of operations involving accessing an object’s methods or properties could result in a NullPointerException:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

If we wanted to make sure we won’t hit the exception in this short example, we would need to do explicit checks for every value before accessing it:

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

As you can see, this can easily become cumbersome and hard to maintain.

To ease this process, let’s take a look at how we can use the Optional class instead, from creating and verifying an instance, to using the different methods it provides and combining it with other methods that return the same type, the latter being where the true power of Optional lies.

已有 2 人翻译此段
我来翻译

Creating Optional Instances

To reiterate, an object of this type can contain a value or be empty. You can create an empty Optional by using the method with the same name:

@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
    Optional<User> emptyOpt = Optional.empty();
    emptyOpt.get();
}

Not surprisingly, attempting to access the value of the emptyOpt variable results in a NoSuchElementException.

To create an Optional object that can contain a value – you can use the of() and ofNullable() methods. The difference between the two is that the of() method will throw a NullPointerException if you pass it a null value as an argument:

@Test(expected = NullPointerException.class)
public void whenCreateOfEmptyOptional_thenNullPointerException() {
    Optional<User> opt = Optional.of(user);
}

As you can see, we’re not completely rid of the NullPointerException. For this reason, you should only use of() when you are sure the object is not null.

If the object can be both null or not-null, then you should instead choose the ofNullable() method:

Optional<User> opt = Optional.ofNullable(user);

已有 2 人翻译此段
我来翻译

Accessing the Value of Optional Objects

One way to retrieve the actual object inside the Optional instance is to use the get() method:

@Test
public void whenCreateOfNullableOptional_thenOk() {
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name);

    assertEquals("John", opt.get());
}

However, as you saw before, this method throws an exception in case the value is null. To avoid this exception, you can choose to first verify if a value is present or not:

@Test
public void whenCheckIfPresent_thenOk() {
    User user = new User("john@gmail.com", "1234");
    Optional<User> opt = Optional.ofNullable(user);
    assertTrue(opt.isPresent());

    assertEquals(user.getEmail(), opt.get().getEmail());
}

Another option for checking the presence of a value is the ifPresent() method. In addition to performing the check, this method also takes a Consumer argument and executes the lambda expression if the object is not empty:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

In this example, the assertion is only executed if the user object is not null.

Next, let’s look at ways in which alternatives for empty values can be provided.

已有 1 人翻译此段
我来翻译

Returning Default Values

The Optional class provides APIs for returning the value of the object or a default value if the object is empty.

The first method you can use for this purpose is orElse(), which works in a very straight-forward way: it returns the value if it’s present, or the argument it receives if not:

@Test
public void whenEmptyValue_thenReturnDefault() {
    User user = null;
    User user2 = new User("anna@gmail.com", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals(user2.getEmail(), result.getEmail());
}

Here, the user object was null, so user2 was returned as a default instead.

If the initial value of the object is not null, then the default value is ignored:

@Test
public void whenValueNotNull_thenIgnoreDefault() {
    User user = new User("john@gmail.com","1234");
    User user2 = new User("anna@gmail.com", "1234");
    User result = Optional.ofNullable(user).orElse(user2);

    assertEquals("john@gmail.com", result.getEmail());
}

The second API in the same category is orElseGet() – which behaves in a slightly different manner. In this case, the method returns the value if one is present, and if not it executes the Supplier functional interface that it receives as an argument, and returns the result of that execution:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

已有 1 人翻译此段
我来翻译

Difference Between orElse() and orElseGet()

At first glance, it might seem as if the two methods have the same effect. However, this is not exactly the case. Let’s create some examples that highlight the similarities as well as the differences in behavior between the two.

First, let’s see how they behave when an object is empty:

@Test
public void givenEmptyValue_whenCompare_thenOk() {
    User user = null
    logger.debug("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.debug("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

private User createNewUser() {
    logger.debug("Creating New User");
    return new User("extra@gmail.com", "1234");
}

In the code above, both methods call the createNewUser() method which logs a message and returns a User object.

The output of this code is:

Using orElse
Creating New User
Using orElseGet
Creating New User

Therefore, when the object is empty and the default object is returned instead, there is no difference in behavior.

已有 2 人翻译此段
我来翻译

Next, let’s take a look at a similar example in which the Optional is not empty:

@Test
public void givenPresentValue_whenCompare_thenOk() {
    User user = new User("john@gmail.com", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

The output this time is:

Using orElse
Creating New User
Using orElseGet

Here, both Optional objects contain a non-null value which the methods will return. However, the orElse() method will still create the default User object. By contrast, the orElseGet() method will no longer create a User object.

This difference can have a significant effect on performance if the operation executed involves more intensive calls, such as a web service call or a database query.

已有 1 人翻译此段
我来翻译

Returning an Exception

Next to the orElse() and orElseGet() methods, Optional also defines an orElseThrow() API – which, instead of returning an alternate value, throws an exception instead if the object is empty:

@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

Here, if the user value is null, an IllegalArgumentException is thrown.

This allows us to have a lot more flexible semantics and decide the exception that gets thrown instead of always seeing a NullPointerException.

Now that we have a good understanding of how we can leverage Optional by itself, let’s have a look at additional methods that can be used to apply transformations and filtering to Optional values.

已有 1 人翻译此段
我来翻译

Transforming Values

Optional values can be transformed in a number of ways; let’s start with map() and flatMap() methods.

First, let’s see an example that uses the map() API:

@Test
public void whenMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com");

    assertEquals(email, user.getEmail());
}

map() applies the Function argument to the value, then returns the result wrapped in an Optional. This makes it possible to apply and chain further operations on the response – such orElse() here.

By comparison, flatMap() also takes a Function argument that is applied to an Optional value, and then returns the result directly.

To see this in action, let’s add a method that returns an Optional to the User class:

public class User {    
    private String position;

    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }

    //...
}

Since the getter method returns an Optional of String value, you can use as the argument for flatMap(), where this is called for an Optional User object. The return will be the unwrapped String value:

@Test
public void whenFlatMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");

    assertEquals(position, user.getPosition().get());
}

已有 1 人翻译此段
我来翻译

Filtering Values

Alongside transforming the values, the Optional class also offers the possibility to “filter” them based on a condition.

The filter() method takes a Predicate as an argument and returns the value as it is if the test evaluates to true. Otherwise, if the test is false, the returned value is an empty Optional.

Let’s see an example of accepting or rejecting a User based on a very basic email verification:

@Test
public void whenFilter_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    assertTrue(result.isPresent());
}

The result object will contain a non-null value as a result of it passing the filter test.

已有 1 人翻译此段
我来翻译
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(11)

世纪末的诗
世纪末的诗
双刃剑,一方面可读性变差了,但是处理过程中安全性变高了。
879360617
879360617
看完还行吧装b用,让别人看不懂你的代码 😂
O
OneToOne
说实话,没啥用。使用Optional变成链式调用和lambda传入。你说这看起来"简洁"了,但却变成了晦涩难懂。性能也不见得提高,更不用说比用if更健壮了。试问一下,先别说接手你代码的人,就算是你自己,许久之后回来看到这种写法,估计你心里都想骂娘。if不管过了多久,都是一看就清晰的。所以用Optional真谈不上什么优化.。...。。..。个人看法,只要你有理有据,带上你的理据,随便喷。
343862324
343862324

引用来自“azheng2017”的评论

没有代码优化习惯的人 ,肯定看不懂用处在哪,而我一个多年严重代码洁癖的人,一看就知道怎么用,就像对一个不讲究卫生的人,你教他物品收纳和收拾房间的方法,一样的道理,都是对牛弹琴,个人追求问题
这个b我给满分
azheng2017
azheng2017
没有代码优化习惯的人 ,肯定看不懂用处在哪,而我一个多年严重代码洁癖的人,一看就知道怎么用,就像对一个不讲究卫生的人,你教他物品收纳和收拾房间的方法,一样的道理,都是对牛弹琴,个人追求问题
程思
程思
在C#当中,一个 ?. 的问题,java 真的需要这么复杂的处理吗?
不懂 java ,麻烦懂的人能介绍下。
f
freezingsky
链式操作确实有文章说到的痛点,倒是有借鉴作用。另外,从代码编写角度来看,如果直接返回一个Optional就相当于告诉调用者,这里有NEP哦,要小心啦。
OldCoffee
OldCoffee
看完了 但是 不是很懂这个具体 有啥用
护士的小黄瓜
护士的小黄瓜
你写这个肯定会有人说我们还在用jdk1,jdk1才是最稳定的
丁永
丁永
比groovy的?.和?:好在哪里?都十年前的东西了啊
返回顶部
顶部
返回顶部
顶部