Optional学习记录

前言

刷视频的时候,经常出现什么
都JDK21啦,JDK22啦,JDK23啦,你不会还不会Optional吧
我知道了它,我收藏了它,我忘记了它
在自学阶段,并没有意识到NPE(NullPointerException)的恐怖,很少遇到
实习后,大量脏数据教你做人
if 写多了之后,感觉这样好low,就从收藏夹把这个翻了出来

Optional是什么?

Optional 是JDK8引入的特性,专用于解决空指针异常(NullPointerException)
老实讲,我是没想到这个特性这么早就出现了,现在到处还会出现各种关于它的介绍
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

Optional的设计也考虑了函数式编程的原则,可以与Lambda表达式和StreamAPI等特性结合使用,可以进行链式调用替代命令式编程的方式通过编写if条件语句检查null值。

基础理解

首先,Optional是一个容器,用于放置可能为空的值,它可以合理而优雅的处理 null。众所周知,null在编程历史上极具话题性,号称是计算机历史上最严重的错误,感兴趣可以读一下这篇文章:THE WORST MISTAKE OF COMPUTER SCIENCE,这里暂且不做过多讨论。在 Java 1.8 之前的版本,没有可以用于表示 null官方 API,如果你足够的谨慎,你可能需要常常在代码中做如下的判断:

if (null != user) {
    //doing something
}
if (StringUtil.isEmpty(string)) {
    //doing something
}

确实,返回值是 null的情况太多了,一不小心,就会产生 NPE,接踵而来的就是应用运行终止,产品抱怨,用户投诉。

1.8 之后,jdk 新增了 Optional来表示空结果。其实本质上什么也没变,只是增加了一个表达方式。Optional表示空的静态方法为 Optional.empty(),跟 null有什么本质区别吗?其实没有。翻看它的实现,Optional中的 value 就是 null,只不过包了一层 Optional,所以说它其实是个容器。用之后的代码可能长这样:

// 1
Optional<User> optionalUser = RemoteService.getUser();
if (!optionalUser.isPresent()) {
   //doing something 
}
User user = optionalUser.get();

// 2
User user = optionalUser.get().orElse(new User());

看起来,好像比之前好了一些,至少看起来没那么笨。但如果采用写法 1,好像更啰嗦了。

如果你对 kotlin 稍有了解,kotlin 的非空类型是他们大肆宣传的"卖点"之一,通过 var param!!在使用它的地方做强制的空检查,否则无法通过编译,最大程度上减少了 NPE。其实在我看来,Optional的方式更加优雅和灵活。同时,Optional也可能会带来一些误解。

下面先说一些在我看来不合适的使用方式:

Bad Practice

  1. 直接使用 isPresent() 进行 if 检查

这个直接参考上面的例子,用 if判断和 1.8 之前的写法并没有什么区别,反而返回值包了一层 Optional,增加了代码的复杂性,没有带来任何实质的收益。其实 isPresent()一般用于流处理的结尾,用于判断是否符合条件。

list.stream()
    .filer(x -> Objects.equals(x,param))
    .findFirst()
    .isPresent()
  1. 在方法参数中使用 Optional

我们用一个东西之前得想明白,这东西是为解决什么问题而诞生的。Optional直白一点说就是为了表达可空性,如果方法参数可以为空,为何不重载呢?包括使用构造函数也一样。重载的业务表达更加清晰直观。

//don't write method like this
public void getUser(long uid,Optional<Type> userType);

//use Overload
public void getUser(long uid) {
    getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
    //doing something
}
  1. 直接使用 Optional.get

Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险。这种可能会出现在那种所谓的着急上线,着急交付,对 Optional也不是很熟悉,直接就用了。这里多说一句,可能有人会反问了:甲方/业务着急,需求又多,哪有时间给他去做优化啊?因为我在现实工作中遇到过,但这两者并不矛盾,因为代码行数上差别并不大,只要自己平时保持学习,都是信手拈来的东西。

  1. 使用在 POJO 中

估计很少有人这么用:

public class User {
    private int age;
    private String name;
    private Optional<String> address;
}

这样的写法将会给序列化带来麻烦,Optional本身并没有实现序列化,现有的 JSON 序列化框架也没有对此提供支持的。

  1. 使用在注入的属性中

这种写法估计用的人会更少,但不排除有脑洞的。

public class CommonService {
    private Optional<UserService> userService;

    public User getUser(String name) {
        return userService.ifPresent(u -> u.findByName(name));
    }
}

首先依赖注入大多在 spring 的框架之下,直接使用 @Autowired很方便。但如果使用以上的写法,如果 userService set 失败了,程序就应该终止并报异常,并不是无声无息,让其看起来什么问题都没有。

Best and Pragmatic Practice

API

在说最佳实践前,让我们来看一下 Optional都提供了哪些常用 API。

  1. empty()

    返回一个 Optional容器对象,而不是 null。建议常用⭐⭐⭐⭐

  2. of(T value)

    创建一个 Optional对象,如果 value 是 null,则抛出 NPE。不建议用⭐⭐

  3. ofNullable(T value)

    同上,创建一个 Optional对象,但 value 为空时返回 Optional.empty()推荐使用⭐⭐⭐⭐⭐

  4. get()

    返回 Optional中包装的值,在判空之前,千万不要直接使用!尽量别用!⭐

  5. orElse(T other)

    同样是返回 Optional中包装的值,但不同的是当取不到值时,返回你指定的 default。看 似很好,但不建议用⭐⭐

  6. orElseGet(Supplier<? extends T> other)

    同样是返回 Optional中包装的值,取不到值时,返回你指定的 default。看似和 5 一样,但推荐使用⭐⭐⭐⭐⭐

  7. orElseThrow(Supplier<? extends X> exceptionSupplier)

    返回 Optional中包装的值,取不到值时抛出指定的异常。阻塞性业务场景推荐使用⭐⭐⭐⭐

  8. isPresent()

    判断 Optional中是否有值,返回 boolean,某些情况下很有用,但尽量不要用在 if 判断体中。可以用⭐⭐⭐

  9. ifPresent(Consumer<? super T> consumer)

    判断 Optional中是否有值,有值则执行 consumer,否则什么都不干。日常情况下请使用这个⭐⭐⭐⭐

  10. ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) JDK9新增

    判断 Optional中是否有值,有值则执行 consumer,否则执行emptyAction日常情况下请使用这个⭐⭐⭐⭐

TIPS

首先是一些基本原则:

  • 不要声明任何Optional实例属性
  • 不要在任何 setter 或者构造方法中使用Optional
  • Optional属于返回类型,在业务返回值或者远程调用中使用
  1. 业务上需要空值时,不要直接返回 null,使用 Optional.empty()
public Optional<User> getUser(String name) {
    if (StringUtil.isNotEmpty(name)) {
        return RemoteService.getUser(name);
    } 
    return Optional.empty();
}
  1. 使用 orElseGet()

获取 value 有三种方式:get() orElse() orElseGet()。这里推荐在需要用到的地方只用 orElseGet()

首先,get()不能直接使用,需要结合判空使用。这和 !=null其实没多大区别,只是在表达和抽象上有所改善。

其次,为什么不推荐 orElse()呢?因为 orElse()无论如何都会执行括号中的内容orElseGet()只在主体 value 是空时执行,下面看个例子:

public String getName() {
    System.out.print("method called");
}

String name1 = Optional.of("String").orElse(getName()); //output: method called
String name2 = Optional.of("String").orElseGet(() -> getName()); //output:

如果上面的例子 getName()方法是一个远程调用,或者涉及大量的文件 IO,代价可想而知。

orElse()就一无是处吗?并不是。orElseGet()需要构建一个 Supplier,如果只是简单的返回一个静态资源、字符串等等,直接返回静态资源即可。

public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 
    return status.orElse(USER_STATUS);
}

//不要这么写
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 
    return status.orElse("UNKNOWN");//这样每次都会新建一个String对象
}
  1. 使用 orElseThrow()

这个针对阻塞性的业务场景比较合适,例如没有从上游获取到用户信息,下面的所有操作都无法进行,那此时就应该抛出异常。正常的写法是先判空,再手动 throw 异常,现在可以集成为一行:

public String findUser(long id) {
    Optional<User> user = remoteService.getUserById(id) ;
    return user.orElseThrow(IllegalStateException::new);
}
  1. 不为空则执行时,使用 ifPresent()

这点没有性能上的优势,但可以使代码更简洁:

//之前是这样的
if (status.isPresent()) {
    System.out.println("Status: " + status.get());
}

//现在
status.ifPresent(System.out::println);
  1. 不要滥用

有些简单明了的方法,完全没必要增加 Optional来增加复杂性。

public String fetchStatus() {
    String status = getStatus() ;
    return Optional.ofNullable(status).orElse("PENDING");
}

//判断一个简单的状态而已
public String fetchStatus() {
    String status = ... ;
    return status == null ? "PENDING" : status;
}

首先,null 可以作为集合的元素之一,它并不是非法的;其次,集合类型本身已经具备了完整的空表达,再去包装一层 Optional也是徒增复杂,收益甚微。例如,map 已经有了 getOrDefault()这样的类似 orElse()的 API 了。

使用详解

创建Optional

Optional(T value), empty(), of(T value), ofNullable(T value)

Optional(T value),即构造函数,它是private权限的,不能由外部调用的。其余三个函数是public权限,供我们所调用。

of(T value)的源码

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

of(T value)函数内部调用了构造函数。根据构造函数的源码我们可以得出两个结论:

通过of(T value)函数所构造出的Optional对象,当Value值为空时,依然会报NullPointerException
通过of(T value)函数所构造出的Optional对象,当Value值不为空时,能正常构造Optional对象。
Optional类 内部还维护一个valuenull的对象,大概就是长下面这样的

public final class Optional<T> {
    //省略....
    private static final Optional<?> EMPTY = new Optional<>();
    private Optional() {
        this.value = null;
    }
    //省略...
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
}

empty()的作用就是返回EMPTY对象

ofNullable(T value)

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}
  1. 首先执行ofNullable()方法,如果T对象为空,执行empty()方法;不为空,执行of(value)方法;
  2. empty()方法,初始化一个空对象Optional(空对象和null不是一回事哈)
  3. of(value)方法,将泛型对象T用于Optional构造方法的参数上,返回一个有值的对象
  4. 经过上面两步,从而保证了Optional不为null,避免了空指针;

相比较of(T value)的区别就是

  1. value值为null时,of(T value)会报NullPointerException异常;

  2. ofNullable(T value)不会throw ExceptionofNullable(T value)直接返回一个EMPTY对象

当我们在运行过程中,不想隐藏NullPointerException。而是要立即报告,这种情况下就用Of函数。但是不得不承认,这样的场景真的很少。

为空的默认处理

orElse(T other)orElseGet(Supplier other)orElseThrow(Supplier exceptionSupplier)
这三个函数,都是在构造函数传入的value值为null时,进行调用的。orElse和orElseGet的用法如下所示,相当于value值为null时,给一个默认值:

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

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

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

案例如下

@Test
public void test() {
    User user = null;
    user = Optional.ofNullable(user).orElse(createUser());
    user = Optional.ofNullable(user).orElseGet(() -> createUser());

    user = null;
    Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));

}
public User createUser(){
    User user = new User();
    user.setName("zhangsan");
    return user;
}

这两个函数的区别:

  1. user值不为null时,orElse函数依然会执行createUser()方法
  2. orElseGet函数并不会执行createUser()方法
  3. orElseThrow,就是value值为null时,直接抛一个异常出去

值的转换

map(Function mapper)flatMap(Function> mapper)
这两个函数做的是转换值的操作

 public final class Optional<T> {
    //省略....
     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));
        }
    }
    //省略...
     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));
        }
    }
}

这两个函数,在函数体上没什么区别。

唯一区别的就是入参

  1. map函数所接受的入参类型为 Function<? super T, ? extends U>
    flapMap 的入参类型为 Function<? super T, Optional<U>>

  2. flatMap() 参数返回值如果是 null 会抛 NullPointerException
    map() 返回EMPTY

User ——> map

public class User {
    private String name;
    public String getName() {
        return name;
    }
}

取name

String name = Optional.ofNullable(user).map(u-> u.getName()).get();

User ——> flatMap

public class User {
    private String name;
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
}

取name

String name = Optional.ofNullable(user).flatMap(u-> u.getName()).get();

存在操作

isPresent()ifPresent(Consumer consumer)

  1. isPresent 即判断value值是否为空
  2. ifPresent 就是在value值不为空时,做一些操作
public final class Optional<T> {
    //省略....
    public boolean isPresent() {
        return value != null;
    }
    //省略...
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
}

注意

if (user != null){
   // TODO: do something
}

//不要下面这样写,这样写,代码结构依然丑陋
User user = Optional.ofNullable(user);
if (Optional.isPresent()){
   // TODO: do something
}

过滤

filter(Predicate predicate)

public final class Optional<T> {
    //省略....
   Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
}

filter 方法接受一个Predicate来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty

如果username的长度是小于6的,则返回。如果是大于6的,则返回一个EMPTY对象

Optional<User> user = Optional.ofNullable(user).filter(u -> u.getName().length() < 6);

实战

如果坚决不想看见 NPE,就不要用 of() get() flatMap(..)

实例一

public String getCity(User user)  throws Exception{
    if(user!=null){
        if(user.getAddress()!=null){
            Address address = user.getAddress();
            if(address.getCity()!=null){
                return address.getCity();
            }
        }
    }
    throw new Excpetion("取值错误");
}

public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("取指错误"));
}

实例二

if(user!=null){
    dosomething(user);
}

Optional.ofNullable(user)
    .ifPresent(u->{
        dosomething(u);
});

实例三

public User getUser(User user) throws Exception{
    if(user!=null && "zhangsan".equals(user.getName())){
        return user;
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}
public User getUser(User user) {
    return Optional.ofNullable(user)
           .filter(u->"zhangsan".equals(u.getName()))
           .orElseGet(()-> {
                User user1 = new User();
                user1.setName("zhangsan");
                return user1;
           });
}

实例四

public static void main(String[] args) {
    List<String> list = null;
    list.forEach(x -> System.out.println(x));
}

public static void main(String[] args) {
    List<String> list = null;
    List<String> newList = Optional.ofNullable(list).orElse(Lists.newArrayList());
    newList.forEach(x -> System.out.println(x));
}

如果list集合不为空,将list集合赋值给newList
如果list集合为空创建一个空对象集合赋值给newList,保证list集合永远不为空,也就避免了空指针异常。

为了更好的理解,分开写了,比较庸俗,实际工作中都是一行搞定

总结

Optional的出现使 Javanull 的表达能力更近了一步,好马配好鞍,合理使用可以避免大量的 NPE,节省大量的人力物力。

反正不要钱,多少信一点。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇