Optional 是你的 Option 吗?

前言

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。OptionalJava 8 引入的新特性,它的出现就是帮助解决程序员经常碰到 NPENullPointerException) 问题,但是由于可读行的原因,很多人在项目当中很少使用它,而是使用更为直观的 if...else,在我看来,Optional 既然被推出了就一定有使用它的价值和意义,有必要去学习了解它,掌握之后写出优雅且略显高级感的代码不在话下!希望 Optional 会是你在编码过程中的一个 Option

介绍

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

这是 Java 8 docs 上的一段原话,总的概况来说就是 Optional 是一个可包含或不包含非空值的容器对象,本质上是一个包装类,里面存放值可以是任何具体 Java 类型,通过 get 方法可以拿到里面的值。

创建实例的静态方法

Optional.empty()

  • 用来创建一个空 Optional 实例
System.out.println(Optional.empty()); // Optional.empty

Optional.of(T value)

  • 用来创建一个具有指定的当前非空值的 Optional 实例

  • 注意传入的值非空,否则会报 NPE

// 对象属性为空,并非 null
User emptyUser = new User();
System.out.println(Optional.of(emptyUser));

// User 对象为空
User nullUser = null;
System.out.println(Optional.of(nullUser)); // throw NullPointerException

// String 对象为空
String nullString = null;
System.out.println(Optional.of(nullString)); // // throw NullPointerException

Optional.ofNullable(T value)

  • 如果值非空返回描述指定值的 Optional ,否则返回 Optional.empty(),不会抛出异常

 

// 对象属性为空,并非 null
User emptyUser = new User();
System.out.println(Optional.ofNullable(emptyUser)); // Optional[User{id=null, name='null', sex='null', age=null, salary=null}]

// User 对象为空
User nullUser = null;
System.out.println(Optional.ofNullable(nullUser)); // Optional.empty

// String 对象为空
String nullString = null;
System.out.println(Optional.ofNullable(nullString)); // Optional.empty

小结

  • 使用 Optional 前需要使用以上三种静态方法创建实例

  • 创建空实例使用 Optional.empty()

  • 如果要创建非空实例,建议使用Optional.ofNullable(),能明确传入参数不为 null ,可以使用 Optional.of() 方式进行创建。

常用的实例方法

isPresent()

  • 用来判断包装值是否存在,如果值存在返回 true ,否则返回 false
Optional<User> opt1 = Optional.of(emptyUser); // Optional[User{id=null, name='null', sex='null', age=null, salary=null}]

Optional<User> opt2 = Optional.ofNullable(nullUser); // Optional.empty

System.out.println(opt1.isPresent() ? "包装值存在" : "包装值不存在"); // 包装值存在
System.out.println(opt2.isPresent() ? "包装值存在" : "包装值不存在"); // 包装值不存在

ifPresent(Consumer<? super T> consumer)

  • 如果 Optional 实例有值则为其调用函数对象 consumer ,否则不做处理

如何理解函数式接口 Consumer ?

// 消费者方法
private static void consumerMethod(User user, Consumer<User> cu) {
    cu.accept(user);
}

// main 主方法中调用
User user01 = new User(42, "石天苟", "女", 16, 4520);
consumerMethod(user01, user1 -> System.out.println(user1.getName() + user1.getSex())); // 石天苟女

Consumer 是消费型接口,接收接口代码段或 Lambda 表达式,通用的方法是用 accept() 方法,该抽象方法对传入的值进行消费,没有返回值。消费的是函数(或是代码段),封装的是逻辑代码。

示例

// opt1、opt2 定义见上
opt1.ifPresent(System.out::println); // User{id=null, name='null', sex='null', age=null, salary=null}
opt2.ifPresent(System.out::println); // 无任何输出
复制代码

get()

  • 如果包装值存在,则从 Optional 封装中取出并返回,否则抛出 NoSuchElementException 异常

示例

User user = opt1.get();
opt2.get(); // throw NoSuchElementException: No value present
System.out.println(user); // User{id=null, name='null', sex='null', age=null, salary=null}
复制代码

orElse(T other)

  • 如果存在则返回该值,否则返回 other

示例

User user02 = new User(20, "李四", "女", 15, 2350);
// opt2 为 null,所以返回 other => user02
User orElse = opt2.orElse(user02); 
System.out.println(orElse); // User{id=20, name='李四', sex='女', age=15, salary=2350}
复制代码

orElseGet(Supplier<? extends T> other)

  • 如果存在则返回该值,否则调用 other 并返回该调用的结果。参数接收 Supplier 接口的实现,Optional 中包含值则返回值,否则返回值从该 Supplier 获取

如何理解函数式接口 Supplier ?

// 提供者/供应商方法
private static User supplierMethod(Supplier<User> su) {
    return su.get();
}

// main 主方法中调用
User user03 = new User(42, "王麻子", "男", 29, 4000);
System.out.println(supplierMethod(() -> user03)); // User{id=42, name='王麻子', sex='男', age=29, salary=4000}
复制代码

Supplier 是提供型接口,接收接口代码段或 Lambda 表达式,通用的方法是用 get() 方法,该抽象方法传入什么数据就得到什么样的结果,可以理解为“种豆得豆,种瓜得瓜”。提供的是函数(或是代码段),封装的是逻辑代码。

示例

orElseThrow(Supplier<? extends X> exceptionSupplier)

  • 返回包含的值(如果存在),否则抛出由 exceptionSupplier 创建的异常

示例

try {
    // opt2 为 null,所以抛出提供的异常
    User elseThrow = opt2.orElseThrow(NullPointerException::new);
    System.out.println(elseThrow);
}catch (Exception e) {
    System.out.println(e); // java.lang.NullPointerException
}
复制代码

filter(Predicate<? super T> predicate)

  • 如果包装值存在且符合 predicate 条件,则返回一个描述该值的 Optional ,否则返回 Optional.empty

示例

Optional<User> opt = Optional.ofNullable(user02)
        .filter(u2 -> u2.getSalary() > 6000);
        
// 没有满足 predicate 的条件
System.out.println(opt); // Optional.empty
复制代码

map(Function<? super T,? extends U> mapper)

源码分析

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));
    }
}
复制代码
  • 如果一个值存在,对它应用提供映射函数,返回的是一个用 Optional.ofNullable 包装后的 Optional

示例

Integer newNameLens =
        Optional.of(user03) // Optional<User>
                .map(u3 -> u3.getName() + '-' + u3.getSex()) // 到这返回的是 Optional<String>
                .map(newName -> newName.trim()) // 到这返回的还是 Optional<String>
                .map(newName -> newName.length()) // 到这返回的是 Optional<Integer>
                .get(); // 取出 Optional 中包装值
                
System.out.println("通过 Map 映射处理后,得到新名字长度:" + newNameLens); // 通过 Map 映射处理后,得到新名字长度:5
复制代码

flatMap(Function<? super T,Optional> mapper)

源码分析

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));
    }
}
复制代码
  • 如果一个值存在,对它应用提供映射函数,返回的是一个用 Objects.requireNonNull 包装后的 Optional,映射函数结果为空会报 NPE

示例

user02.setName("诸葛大力");
String firstName = Optional.ofNullable(user02) // // 到这返回的是 Optional<User>
        .flatMap(u2 -> Optional.of(u2.getName())) // 到这返回的是 Optional<String>
        .flatMap(name -> {
            String familyName;
            if (name.length() <=3) {
                familyName = name.substring(0, 1);
            } else {
                familyName = name.substring(0, 2);
            }
            return Optional.of(familyName);
        }) // 到这返回的还是 Optional<String>

System.out.println("经过 flatMap 映射处理之后,用户名姓氏:" + firstName); // 诸葛
复制代码

小结

  • 当包装值存在时,都能拿到描述该值的 Optional

  • 当包装值可能不存在时,注意避免 NPE 异常,创建 Optional 包装类时要使用静态 ofNullable() ,链式末尾加 orElse...() 走为空值时的逻辑或者抛出对应的异常信息,此外,也可以用 isPresent() 判空、ifPresent() 方法将可能存在的包装值消费掉,此时值为空也没关系,并不会做处理、报 NPE

  • Optional 实例方法 map()flatMap() 区别在于,一个映射函数结果是具体值且可以为空,后一个则是 Optional 且不能为空,否则 NPE 安排

场景运用

空值转换

String avatarUrl = null;
User user = new User("HUALEI", url);

String userAvatarUrl = Optional.ofNullable(user)
                       .map(u -> u.getUserAvatar()).orElse("https://p3-passport.byteacctimg.com/img/user-avatar/7c53d8cf8b3f3de699f1db654b88063d~300x300.image");

user.setUserAcatar(avatarUrl);
复制代码

如果用户未设置头像,则采用默认头像。

Integer salary = Optional.ofNullable(emptyUser)
        .map(u -> u.getSalary())
        .orElse(0);

emptyUser.setSalary(salary);
复制代码

如果员工没有初始工资,则将默认为 0。

值过滤

验证手机号码格式,常规写法:

String phone = "12345678910";
String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0,5-9]))\d{8}$";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(phone);
boolean isMatch = m.matches();
if(isMatch){
    System.out.println("手机号格式正确");
} else {
    System.out.println("手机号码格式错误");
}
复制代码

使用 Optional.filter() 方法过滤

boolean isValid = Optional.ofNullable(user)
                                .filter(u -> {
                                    Pattern pattern = Pattern.compile(regex);
                                    return pattern.matcher(u.getPhone()).matches();
                                })
                                .isPresent();

assertTrue(isValid);
复制代码

混合操作

员工列表

User user01 = new User(42, "石天苟", "女", 16, 4520);
User user02 = new User(20, "李四", "女", 15, 2350);
User user03 = new User(42, "王麻子", "男", 29, 4000);
List<User> users = new ArrayList<User>() {{
    add(user01);
    add(user02);
    add(user03);
}};
复制代码

求员工的总工资,之前我们可能会这样写:

// 计算工资累加器
int sumSalary = 0;

// 在增强 for 循环中累加每个非空用户的工资
if (users != null) {
    for (User u : users) {
        if(u != null) {
            sumSalary += u.getSalary();
        }
    }
}

System.out.println("员工总工资:" + sumSalary); // 员工总工资:10870
复制代码

看了这篇 流上的函数式操作 - 掘金 (juejin.cn) 的你,可能会这样写:

Integer sumSalary = 0;

if (users != null) {
    sumSalary = users.stream()
            .filter(Objects::nonNull)
            .map(User::getSalary)
            .reduce(0, (sum, currentValue) -> sum + currentValue);
}

System.out.println("员工总工资:" + sumSalary); // 员工总工资:10870
复制代码

但你会这样写吗?

29B28A11.jpg

Integer sumSalary = Optional.ofNullable(users).map(us -> {
    Integer sum = 0;
    for (User user : us) {
        sum += Optional.ofNullable(user).map(User::getSalary).orElse(0);
    }
    return sum;
}).orElse(0);

System.out.println("员工总工资:" + sumSalary); // 员工总工资:10870

更多建站及源码交易信息请见 GoodMai 好买网

壹脉销客V3.0.0版本,适合政企单位的宣传展示名片系统Java源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值