多此一举!请不要这样用Java 8 Optional

文章讨论了Java8中Optional类的使用,指出常见的错误用法,如isPresent()和get()的组合并未带来简化,反而增加复杂度。正确的使用方式是结合orElseThrow()等Stream风格的方法。同时,不应在确定有返回值的方法中使用Optional,也不应将Optional作为参数,除非必要。文章还提醒了Optional作为类字段和在Collection、Map中的使用注意事项。
摘要由CSDN通过智能技术生成

我们都知道程序中尽量不要返回null,而Java 8 新加入了Optional 类别可避免NullPointerException 问题与繁琐的null check,可以让程序逻辑看起来更简洁、易读,也能清楚表达method 可能没有结果值。但我却看到了不少错误的用法,反而让Optional 显得多此一举。本篇探讨这些错误的用法,以及如何正确使用。

isPresent() 和 get()

假设有一个 studentService 可利用id 查询学生资料,我们为了避免return null 而后续可能导致NPE,我们就必需在 studentService.readById 回传结果时先做null check,因此传统写法会像这样:

public Student readById(String id) {
    Student student = studentService.readById(id);
    if (student != null) {
        return student;
    } else {
        throw new NotFoundException(id); 
    }
}

若改成 Optional 写法,并将 studentService.readById 改为回传 Optional 后,有些人可能会写成这样:

public Student readById(String id) {
    Optional<Student> student = studentService.readById(id);
    if (student.isPresent()) {
        return student.get();
    } else {
        throw new NotFoundException(id); 
    }
}

很不幸的是,这应该是最常见的错误用法了,我们不难发现上面的isPresent(),get()和传统写法本质上是一样的,且增加了不必要的复杂度,可谓多此一举。

正确使用Optional 方式如下:

public Student readById(String id) {
    return studentService.readById(id).orElseThrow(() -> new NotFoundException(id));
}

orElseThrow 会判断 Optional 的內容,若有值时则直接返回 Student;若沒有,则拋出异常。其实Optional 是与 Java 8 stream 写法相辅相成的,所以使用 Optional 时搭配如 filter(), map(), orElseThrow() 等的 stream 风格的写法会比较适合

一定有值,却依然使用 Optional

Optional 设计的意义就是用来表示方法 的返回值可能会是空的。但在某些一定会返回值得情況下,开发者却依然使用 Optional,这就造成了过度包装。承上学生系统的例子,假设我们要查詢全体学生中的第一名:

public Optional<Student> readTopScoreStudent() {
    // ...
}

正常来说,这个系统并不会沒有学生资料(否则一切都是空谈),因此这个 方法 肯定会有返回值,不需使用 Optional。通常需要通过 code review 才能发现类似的问题

作为参数

有些人会将 Optional 作为参数,意图表示这个参数可能是非必要的:

public void setName(Optional<String> name) {
    if (name.isPresent()) {
        this.name = name.get();
    } else {
        this.name = "无名氏";
    }
}

但这是个不好的写法,因为这里的 Optional 参数有3种可能的值:

  • 有內容值的 Optional
  • 可选的.empty()
  • 无效的

Optional 也有可能是个 null,当然有机会引发 NPE,让人更摸不着头绪,因此请不要使用 Optional 作为差不是参数。此外,这样的写法会让 方法的调用者 很痛苦,因为他们必需将参数多包装一层 Optional,变得更加不容易使用

setName(Optional.of("Jason"));
setName(Optional.empty());

因此,比较好的设计是通过方法重载,让参数有值或沒有值结果果更加明确:

public void setName() {
    this.name = "無名氏";
}

public void setName(String name) {
    this.name = name;
}

另外,有人说,Optional 若作为 Spring controller 的参数,则更能表达该参数是非必要的,例如:

@RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET, produces="text/xml")
public String showLoginWindow(@PathVariable("id") String id,
                              @RequestParam("username") Optional<String> username,
                              @RequestParam("password") Optional<String> password) { ... }

在Spring 4.1.1 后已经可以妥善处理这里的Optional,它将不会是null,再加上它是controller,所以也不会难以被调用,因此有些人觉得这种作法比较好,这就见仁见智了。

作为class field

public class Student {

    private Optional<String> name;
    // ...
}

因为Optional 是设计用来方法的返回值,因此它并没有实现序列化 Serializable 接口,在特定状况下(如网路传输)需要序列化时将会出现问题

Collection and Optional

因为Optional 本身就是一個容器,如果內容又是另一個容器,例如 Optional<List>,这不仅比较复杂以外,在语义上还代表着三种可能的返回值:

  • 一个有內容的 List
  • 一个空的 List
  • Optional.empty()

这样容易造成程序复杂与混淆,比较好的方式是:如果真的沒有返回值,那就返回一个空的集合就好了

public List<Student> readAllStudentsInClass(String classId) {
    // ... 
    return result.isEmpty() ? Collections.emptyList(): new ArrayList<>(result);
}

Map and Optional
不要將 Optional 放入 Map,例如 Map<String, Optional>,原因和上述类似,在调用 map.get(key) 的返回值:

  • Optional (可能有 Student 與可能沒有 Student)
  • null

像这种错误用法都会提高不必要的复杂性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

资料小助手

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

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

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

打赏作者

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

抵扣说明:

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

余额充值