Optional用好了,缓解了多年NullPointerException恐惧。

Hi,大家好,我是抢老婆酸奶的小肥仔。

在我们日常开发中NullPointerException是永远绕不过去的坑,代码写的不好,天天都会被它折磨的死去活来,今天我们来介绍下Optional,这个能让我们缓解NullPointerException恐惧的工具。

在没有Optional之前,我们为了避免NullPointerException很多时候都是使用if-else-if来进行条件判断,然后根据条件来获取对应数据。例如:获取赵一所在的职位名称。

java 复制代码
List<Expert> experts = Arrays.asList(
                new Expert("jiashn","男",20,new Position("总经理",1)),
                new Expert("张三","女",21,new Position("财务总监",2)),
                new Expert("王五","女",26,new Position("会计",3)),
                new Expert("李四","男",24,new Position("开发部经理",2))
        );
//获取赵一所在的职位名称
if(CollectionUtils.isNotEmpty(experts)){
    for (Expert expert : experts) {
        if (Objects.equals("赵一",expert.getName())){
            Position position = expert.getPosition();
            if (Objects.nonNull(position)){
                System.out.println(position.getName());
            }
        }
    }
}

使用optional就会优雅很多,也会尽量的避免NPE。如:

java 复制代码
String name = Optional.of(users.stream().filter(user -> Objects.equals("赵一", user.getName())).findFirst())
                .get()
                .map(SystemUser::getPosition)
                .map(Position::getName)
                .orElse("");
        System.out.println("Optional获取职位名称:" + name);

上述代码对比中,我们可以清晰感觉到使用Optional代码比较优雅,也没有大量代码判空,即使数据不存在也没报NPE,避免了NPE.

Optional是JDK1.8后引入的一个新特性,通常被认为是用于缓解java中NPE问题。Optional作为容器,可以保存类型T的值,也可以直接保存null。

接下来我们介绍下Optional的一些方法。

Optional方法

准备工作,创建一个有数据的系统用户,一个为null的数据。

java 复制代码
/**
 * @author: jiangjs
 * @description: 系统用户
 * @date: 2023/5/8 11:40
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SystemUser {
    private String name;
    private String gender;
    private Integer age;
    private Position position;

    public Optional<Position> optionalPosition(){
        return Optional.ofNullable(position);
    }
}

/**
 * @author: jiangjs
 * @description: 职位
 * @date: 2023/5/8 11:20
 **/
@Data
@AllArgsConstructor
public class Position {

    /**
     * 职位名称
     */
    private String name;
    /**
     * 职位等级
     */
    private Integer level;

}
java 复制代码
 SystemUser systemUser = new SystemUser("王五","女",26,new Position("会计",3));
 SystemUser user = null;

empty

static<T> Optional<T> empty() :静态方法,返回空的Optional。

源码:

java 复制代码
private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

实现:

java 复制代码
//empty
Object o = Optional.empty();
System.out.println("empty:" + o);

输出:

💡 empty:Optional.empty

of、ofNullable

static <T> Optional<T> of(T value) :创建一个非空的Optional,当T为null时,则抛出NPE。

static <T> Optional<T> ofNullable(T value) :如果T不为空,则创建指定值的Optional,否则返回空的Optional。

java 复制代码
//of ofNullable
Optional<SystemUser> systemUserOptional = Optional.of(systemUser);
System.out.println("systemUserOptional:" + systemUserOptional);

Optional<SystemUser> userOptional = Optional.of(user);
System.out.println("userOptional:" + userOptional);

Optional<SystemUser> optionalSystemUser = Optional.ofNullable(systemUser);
System.out.println("optionalSystemUser:" + optionalSystemUser);

Optional<SystemUser> optionalUser = Optional.ofNullable(user);
System.out.println("optionalUser:" + optionalUser);

输出:

💡 systemUserOptional:Optional[SystemUser(name=王五, gender=女, age=26, position=Position(name=会计, level=3))]

💡 java.lang.NullPointerException
💡 optionalSystemUser:Optional[SystemUser(name=王五, gender=女, age=26, position=Position(name=会计, level=3))]

💡 optionalUser:Optional.empty

Optional.of(user):报了空指针异常,这是因为of在创建Optional时,会对T进行判空处理。源码:

java 复制代码
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

Optional.ofNullable(user):使用了三目运算符,如果T为空,则调用empty(),返回空的Optional。 源码:

java 复制代码
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

isPresent()

isPresent() :判断是否包含指定值,包含返回true,反之返回false。

java 复制代码
//isPresent
boolean present = Optional.of(systemUser).isPresent();
System.out.println("systemUser是否存在:" + present);

boolean judge = Optional.ofNullable(user).isPresent();
System.out.println("user是否存在:" + judge);

输出:

💡systemUser是否存在:true

💡 user是否存在:false

isPresent():直接获取Optional中的T来判空。源码:

java 复制代码
 public boolean isPresent() {
    return value != null;
}

注:在写代码时,不要即用了if,又用isPresent来进行判断,如:if (Optional.ofNullable(user).isPresent()){业务代码},这样消除不了繁琐的if判断,也创建了Optional对象。

orElse、orElseGet

T orElseGet(Supplier<? extends T> other) :如果调用对象包含值,则返回该值,否则返回other。

T orElse(T other) :如果调用对象包含值,则返回该值,否则返回other。

java 复制代码
//orElseGet,orElse
SystemUser orElseGetUser = Optional.ofNullable(user).orElseGet(UseOptional::getSystemUser);
System.out.println("orElseGetUser:" + orElseGetUser);
SystemUser orElseUser = Optional.ofNullable(user).orElse(getSystemUser());
System.out.println("orElseUser:" + orElseUser);
System.out.println("-------------------------------------------------------------");

SystemUser orElseGetSystemUser = Optional.of(systemUser).orElseGet(UseOptional::getSystemUser);
System.out.println("orElseGetSystemUser:" + orElseGetSystemUser);
SystemUser orElseSystemUser = Optional.of(systemUser).orElse(getSystemUser());
System.out.println("orElseSystemUser:" + orElseSystemUser);

输出:

默认数据:SystemUser(name=李四, gender=男, age=24, position=Position(name=开发部经理, level=2))

💡 orElseGetUser:SystemUser(name=李四, gender=男, age=24, position=Position(name=开发部经理, level=2))

默认数据:SystemUser(name=李四, gender=男, age=24, position=Position(name=开发部经理, level=2))

💡 orElseUser:SystemUser(name=李四, gender=男, age=24, position=Position(name=开发部经理, level=2))


💡 orElseGetSystemUser:SystemUser(name=王五, gender=女, age=26, position=Position(name=会计, level=3))

默认数据:SystemUser(name=李四, gender=男, age=24, position=Position(name=开发部经理, level=2))

💡 orElseSystemUser:SystemUser(name=王五, gender=女, age=26, position=Position(name=会计, level=3))

当调用对象不包含值时,虽然两者都是返回other,但是从定义中我们可以看到orElseGet是使用了Supplier函数式编程,而orElse直接使用T。上面例子也说明了orElse会提前创建T而orElseGet会在判断调用对象没有时才会调用Supplier来创建other。

orElse与orElseGet区别:

💡 orElse:不管调用对象是否存在,都会提前创建T。

💡 orElseGet:只有当调用对象不存在时,才会使用Supplier创建返回值。

get

get():即获取Optional的对象数据,如果对象数据不存在则抛出异常:NoSuchElementException

java 复制代码
//get
SystemUser getSystemUser = Optional.ofNullable(systemUser).get();
System.out.println("getSystemUser:" + getSystemUser);
SystemUser getUser = Optional.ofNullable(user).get();
System.out.println("getUser:" + getUser);

输出:

💡 getSystemUser:SystemUser(name=王五, gender=女, age=26, position=Position(name=会计, >level=3))

💡 Exception in thread "main" java.util.NoSuchElementException: No value present

at java.util.Optional.get(Optional.java:135)

at com.jiashn.springbootproject.useUtil.UseOptional.main(UseOptional.java:86)

map、flatMap

<U> Optional<U> map(Function<? super T, ? extends U> mapper) :如果有值,则进行数据处理,如果没有值,则返回Optional.empty()。

<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) :与map类似,要求返回值必须是Optional。

java 复制代码
//map
String sysUserName = Optional.of(systemUser).map(SystemUser::getName).get();
System.out.println("sysUserName:" + sysUserName);
Optional<String> userName = Optional.ofNullable(user).map(SystemUser::getName);
System.out.println("userName:" + userName);
//flatMap
String name = Optional.of(systemUser)
        .flatMap(SystemUser::optionalPosition)
        .map(Position::getName).get();
System.out.println("name:" + name);

输出:

💡 sysUserName:王五

💡 userName:Optional.empty

💡 name:会计

上述就是Optional提供一些方法,我们在实际开发中应灵活运用,通常Optional会跟Stream进行联合使用。例如:获取上述集合性别为女的,然后获取过滤后集合的第一个元素,数据不存在时创建新的对象。

java 复制代码
Expert expert = Optional.of(experts.stream().filter(ex -> Objects.equals("女",ex.getGender())).findFirst())
                .get()
                .orElseGet(Expert::new);

这样子在后面使用expert就能避免出现NPE.

jdk引入了Optional能让我们快捷的获取比较复杂的数据结构中的数据,同时也能有效避免NPE,当然Optional方法单独使用可能效果不大,但是组合使用时会让我们事半功倍。

相关推荐
java干货14 分钟前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
一只叫煤球的猫18 分钟前
MySQL 8.0 SQL优化黑科技,面试官都不一定知道!
后端·sql·mysql
SoFlu软件机器人20 分钟前
智能生成完整 Java 后端架构,告别手动编写 ControllerServiceDao
java·开发语言·架构
写bug写bug1 小时前
如何正确地对接口进行防御式编程
java·后端·代码规范
Cyanto1 小时前
Java并发编程面试题
java·开发语言·面试
不超限1 小时前
Asp.net core 使用EntityFrame Work
后端·asp.net
在未来等你2 小时前
互联网大厂Java求职面试:AI大模型与云原生技术的深度融合
java·云原生·kubernetes·生成式ai·向量数据库·ai大模型·面试场景
豌豆花下猫2 小时前
Python 潮流周刊#105:Dify突破10万星、2025全栈开发的最佳实践
后端·python·ai
sss191s2 小时前
Java 集合面试题从数据结构到 HashMap 源码剖析详解及常见考点梳理
java·开发语言·数据结构