Java Optional总结

概述

Optional的本意是为了避免null带来的错误,为我们提供一个可以明确表示空值的有限的机制。

基础理解

Optional是一个容器,用于放置可能为空的值,它可以合理而优雅的处理null。
Jdk1.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.orElse(new User());

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

不合适的使用场景

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

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

boolean aaa = list.stream().filter(o -> Objects.equals(o, param)).findFirst().isPresent();

在方法参数中使用Optional

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

    public void getUser(String uuid, Optional<UserType> optional) {     
    }

    public void getUser(String uuid) {
        getUser(uuid,null);
    }

    public void getUser(String uuid, UserType userType) {
        // do something
    }

直接使用Optional.get

Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用Optional.get()和不做任何判断一样,十分危险。这可能会出现在那种所谓的急着上线,着急交付,对Optional也不是很熟悉,直接就用了。所以这就要我们平时保持学习,都是信手拈来的东西。

使用在注入的属性中

这种写法估计用的人很少,但是不排除脑洞大开的。

public class CommonService {
    private Optional<UserService> userService;
    
    public User getUser(String name) {
        return userService.ifPresent(u -> u.findByName(name));
    }
}

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

Optional的API

empty()

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

// empty.get()值为null
Optional<Object> empty = Optional.empty();

of(T value)

创建一个Optional对象,如果value是null,则抛出NPE(null point exception)。不建议使用。

ofNullable(T value)

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

get()

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

orElse(T other)

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

orElseGet(Supplier<? extends T> other)

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

orElseThrow(Supplier<? extends X> exceptionSupplier)

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

isPresent()

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

ifPresent(Consumer<? super T> consumer)

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

提示

一些基本原则:

  • 不要声明任何Optional实例属性。
  • 不要再任何setter或者构造方法中使用Optional
  • Optional属于返回类型,在业务返回值或者远程远程调用中使用

最佳实践

对象可空,需要获取对象属性

Optional.ofNullable(resData).orElse(Collections.emptyMap()).get("data")

业务上需要空值时,不要直接返回null,使用Optional.empty()

    public Optional<String> getOptionResult(String user) {
        if (Objects.nonNull(user)) {
            return Optional.ofNullable(user);
        }
        return Optional.empty();
    }

使用orElseGet()

使用orElseGet()主要是从性能的角度考虑。
获取value有三种方式:get(),orElse(),orElseGet()。这里推荐在需要用的地方只有orElseGet()。
首先,get()不能直接使用,需要结合判空使用。这和!=null其实没有多大区别,只是在表达和抽象上有所改善。
其次,为什么不推荐orElse()呢?因为orElse()不论如何都会指向括号中内容,orElseGet()只在主体value是null时才执行:

    public String getResult() {
        System.out.println("call result");
        return "result";
    }

    public void testOrElseGet() {
        // out:
        Optional.ofNullable("result").orElseGet(() -> {
            System.out.println("orElseGet call exe");
            return "call";
        });
        // out: call result
        Optional.ofNullable("result").orElse(getResult());
    }

如果上面的例子getResult()方法是一个远程调用,或者涉及到大量的文件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对象
}

使用orElseThrow()

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

    public User testThrow(User user) throws Exception {
        return Optional.ofNullable(user).orElseThrow(Exception::new);
    }

不为null则执行时,使用ifPresent

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

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

//现在
status.ifPresent(System.out::println);

不要滥用

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

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

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

使用map从Optional对象中提取和转换值

从对象中提取信息是一种比较常见的模式。比如,你可能想从insurance公司对象中提取公司的名称。提取名称之前,你需要检查insurance对象是否为null,代码如下:

        String name = null;
        if (insurance != null) {
            name = insurance.getName();
        }

为了支持这种模式,Optional提供了一个map方法。它的工作方式如下:

        final Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
        final Optional<String> name = optInsurance.map(Insurance::getName);

从概念上,这与流的map方法相差无几。map操作会将提供的函数应用于流的每个元素。你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。下图展示把一个正方形转换为三角形的函数,分别传递给正方形和Optional正方形流的map方法之后的结果。
在这里插入图片描述

使用flatMap链接Optional对象

可以把flatMap理解为打平 Optional<Optional>为Optional。
由于我们刚刚学习了如何使用map,你的第一反应可能是我们可以利用map重写之前的代码,如下所示:

    @Data
    static class Insurance {
        private String name;
    }

    @Data
    static class Car {
        private Optional<Insurance> insurance;
    }

    @Data
    static class Person {
        private Optional<Car> car;
    }
        final Optional<Person> optPerson = Optional.ofNullable(person);
        final Optional<String> name = 
        		optPerson.map(Person::getCar)
                .map(Car::getInsurance)
                .map(Insurance::getName);

不幸的是,这段代码无法通过编译。因为optPerson是Optional类型的变量,调用map方法没问题,但getCar返回的是一个Optional类型的对象,这意味着map操作的结果是一个Optional<Optional>类型的对象。因
此,它对 getInsurance 的调用是非法的,因为最外层的 optional 对象包含了另一个 optional
对象的值,而它当然不会支持 getInsurance 方法。
在这里插入图片描述
所以,我们该如何解决这个问题呢?让我们再回?一下你??在流上使用过的模式:
flatMap 方法。使用流时, flatMap 方法接受一个函数作为参数,这个函数的返回值是另一个流。
这个方法会应用到流中的每一个元素,最终形成一个新的流的流。但是 flagMap 会用流的内容替
换每个新生成的流。换句话说,由方法生成的各个流会被合并或者?平化为一个单一的流。这里
你希望的结果其实也是类似的,但是你想要的是将两层的 optional 合并为一个。
在这里插入图片描述
因此我们可以使用下面代码获取car的保险公司名称:

        final Optional<Person> optPerson = Optional.ofNullable(person);
        final String name = 
        		optPerson.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("Unknow");

在这里插入图片描述
截止目前为止,返回的Optional可能有两种情况:如果调用链上的任何一个方法返回一个空的Optional,那么结果就为空,否则返回的值就是你期望的保险公司的名称。

使用filter剔除特定的值

你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要查保险公司的名
称是否为“Cambridge-Insurance”。为了以一种安全的方式进行这些操作,你首先需要确定引用指
向的 Insurance 对象是否为 null ,之后再调用它的 getName 方法,如下所示:

Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}

使用 Optional 对象的 filter 方法,这段代码可以重构如下:

Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->  "CambridgeInsurance".equals(insurance.getName()))
			.ifPresent(x -> System.out.println("ok"));

filter 方法接受一个谓词作为参数。如果 Optional 对象的值存在,并且它符合谓词的条件,
filter 方法就返回其值;否则它就返回一个空的 Optional 对象。如果你还记得我们可以将
Optional 看成最多包含一个元素的 Stream 对象,这个方法的行为就非常清晰了。如果 Optional
对象为空,它不做任何操作,反之,它就对 Optional 对象中包含的值施加谓词操作。如果该操
作的结果为 true ,它不做任何改变,直接返回该 Optional 对象,否则就将该值过滤掉,将
Optional 的值置空。

异常与Optional的对比

由于某种原因,函数无法返回某个值,这时除了返回 null ,Java API比较常见的替代做法是
抛出一个异常。这种情况比较典型的例子是使用静态方法 Integer.parseInt(String) ,将
String 转换为 int 。在这个例子中,如果 String 无法解?到对应的整型,该方法就?出一个
NumberFormatException 。最后的效果是,发生 String 无法转换为 int 时,代码发出一个遭遇
非法参数的信号,唯一的不同是,这次你需要使用 try / catch 语句,而不是使用 if 条件?断来
控制一个变量的值是否非空。
你也可以用空的 Optional 对象,对遭遇无法转换的 String 时返回的非法值进行建模,这时
你期望 parseInt 的返回值是一个 optional 。我们无法修改最初的Java方法,但是这无?我们进
行需要的改进,你可以实现一个工具方法,将这部分逻辑?装于其中,最终返回一个我们希望的
Optional 对象,代码如下所示。

public static Optional<Integer> stringToInt(String s) {
	try {
		return Optional.of(Integer.parseInt(s));
	} catch (NumberFormatException e) {
		return Optional.empty();
	}
}

我们的建议是,你可以将多个类似的方法?装到一个工具类中,让我们称之为 Optiona-
lUtility 。通过这种方式,你以后就能直接调用 OptionalUtility.stringToInt 方法,将
String 转换为一个 Optional 对象,而不再需要记得你在其中封装了笨拙的
try / catch 的逻辑了。

总结

Optional的出现使Java对null的表达式更进一步,希望大家能够掌握API的使用方法和原理,合理使用可以避免大量的NPE,节省大量的人力物力。
Optional类支持多次方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java OptionalJava 8 引入的一个类,用于解决经常出现的空指针异常。它的设计初衷是希望提供一种简洁的方式来处理可能为空的对象。 然而,Optional 也存在一些错误的用法,下面列举了一些常见的错误用法: 1. 非必要地使用 Optional:有些开发者过度使用 Optional,把所有的对象都包装成 Optional,这样会导致代码变得复杂且难以理解。只有当对象确实可能为空时,才应考虑使用 Optional。 2. 错误的使用 ifPresent 方法:ifPresent 方法用于判断 Optional 对象是否有值,如果有值则执行指定的方法。有些开发者错误地使用 ifPresent 方法来执行一些复杂的逻辑,这样会导致代码变得混乱。正确的做法是使用 map 或 flatMap 方法来进行链式操作。 3. 对空值的处理不当:如果没有正确处理 Optional 对象为空的情况,仍然会导致空指针异常的发生。应该使用 isPresent 方法或者使用 orElseThrow 方法来处理空值的情况。 4. 对 Optional 对象的滥用:有些开发者滥用 Optional 对象,把 Optional 当作方法的返回类型,这样会增加方法的复杂性,并且会误导调用者。正确的做法是根据具体情况判断是否需要使用 Optional总结来说,Java Optional 是一种处理可能为空对象的有效方式,但是需要注意一些错误的用法。我们应该根据具体情况,合理使用 Optional,并且避免过度使用 Optional,以及注意处理空值的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值