0.由来
实际 Java
开发过程中,尝试访问空引用的属性或者调用空引用的方法,会报 空指针异常(NullPointerException
)。处理可能为 null
的值时,需要增加很多 条件判定,比如:
💗User:
java">@Data
@AllArgsConstructor
public class User {String name;String fullName;
}
💙UserRepository:
java">public class UserRepository {public User findUserByName(String name){if(name.equals("Shwen")){return new User("Shwen","Shwen shen");} else {return null;}}
}
👉OptionalTest:
java">public class OptionalTest {public static void main(String[] args) {UserRepository userRepository = new UserRepository();User user = userRepository.findUserByName("Shwen");if(user!=null){System.out.println(user.getFullName());} else {User defaultUser = new User("Stark", "Tony Stark");System.out.println(defaultUser.getFullName());}}
}
代码会有很多的条件判断,难以阅读与维护。
1.概述
Optional
类引入了一种显式的方式来处理可能为空的对象,强制程序员在可能为空的情况下进行显式的处理,以避免空指针异常。
Optional
是一个容器对象,用于封装可能为 null
的值。当一个方法声明返回 Optional<T>
时,它将明确地表示该方法可能会返回 null
。这种设计模式有助于减少空指针异常的风险,并鼓励更好的编程实践。
Optional
类提供了一系列方法来方便地操作内部的值。常用的方法有get
、orElse
、orElseGet
、orElseThrow
等。
Optional
的设计也考虑了 函数式编程
的原则,可以与 Lambda
表达式和 StreamAPI
等特性结合使用,可以进行链式调用替代命令式编程的方式(通过编写if条件语句检查null值)。
2.Optional对象的方法
2.1 创建Optional实例
of
:创建不为 null 的对象,若 value 为 null,则抛出 NullPointerException 异常。
java">Optional.of(new User("Shwen","Shwen shen"));
ofNullable
:创建可能为 null 的对象
java">Optional.ofNullable(new User("Shwen","Shwen shen"));
empty
:创建的对象为 null
java">Optional.empty();
💙改造UserRepository:
java">public class UserRepository {public Optional<User> findUserByName(String name){if(name.equals("Shwen")){return Optional.ofNullable(new User("Shwen","Shwen shen"));} else {return Optional.empty();}}
}
2.2 判断方法
isPresent
:用于检查optional
内是否存在值,返回为boolean
,存在为true
,不存在为false
。
java">boolean flag = optionalUser.isPresent();
2.3 获取方法
get
:若取值对象为null
,会报NoSuchElementException
异常(并非NullPointerException
异常)。
java">User user = optionalUser.get();
orElse
:用于获取值或在值为null
的情况下提供一个默认值。
java">public class OptionalTest {public static void main(String[] args) {UserRepository userRepository = new UserRepository();Optional<User> optionalUser = userRepository.findUserByName("Shwen2");// orElse 用于获取值,或在值为 null 的情况下提供一个默认值。User user = optionalUser.orElse(new User("Stark","Tony Stark"));System.out.println(user.getFullName());}
}
optionalUser
有值的情况下,orElse
返回值;值为 null
的情况下,返回默认值 new User("Stark","Tony Stark")
。
orElseGet
:功能同orElse
,区别在于方法的参数为Supplier
的函数式接口,需要使用Lambda
表达式实现。
java">User user = optionalUser.orElseGet(() -> new User("Stark","Tony Stark"));
📌orElse
和 orElseGet
区别如下:
orElse
入参是对象,orElseGet
入参是Supplier
的函数式接口orElse
即使获取的数据不为null
,仍会创建一个新User
对象。而orElseGet
不会创建。因此,最好使用只有读取的数据为null
的时候才会新建对象的orElseGet
方法。
orElseThrow
:用于在Optional
对象中的值为null
时抛出一个指定的异常。
java">User user = optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
2.4 判断获取方法
ifPresent
:参数中若对象不为null
,则会执行Labmda
中的方法;若参数对象为null
,则不会执行Labmda
中的方法,也不会报错。
java">optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
ifPresentOrElse
:当我们希望值为null
时进行其他操作,需要使用ifPresentOrElse
方法。⚠️注:ifPresentOrElse
方法是 Java 9 引入的。
java">optionalUser.ifPresentOrElse(user -> System.out.println(user.getFullName()),()->System.out.println("User not found"));
filter
:若满足filter
方法中的条件,则会返回包含值的Optional
对象,如果不满足,则返回空的Optional
对象。
java">Optional<User> optionalUser2 = optionalUser.filter(user -> user.getFullName().equals("Shwen shen"));
System.out.println(optionalUser2.isPresent());
2.5 map与flatMap方法
2.5.1 map
java">public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
java">public class OptionalTest {public static void main(String[] args) {UserRepository userRepository = new UserRepository();Optional<User> optionalUser = userRepository.findUserByName("Shwen2");Optional<String> optional = optionalUser.map(User::getFullName);System.out.println(optional.orElse("User not found"));}
}输出:
User not found
对 Optional
中的值进行转换,若值为 null
,则 map
方法什么也不会做,直接返回空的 Optional
对象。map
方法不会改变原始的 Optional
对象,而返回新的 Optional
对象,因此可以链式调用进行多个转换操作。
2.5.2 flatMap
java">public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
flatMap
方法用于扁平化嵌套的 Optional
结构,以避免引入不必要的嵌套层级,具体为 flatMap
的转换函数返回的必须是另一个 Optional
对象,意味着 flatMap
方法可以用于嵌套的 Optional
情况,可以将两个为嵌套关系的 Optional
对象转换为一个。
如果原始的 Optional
对象为 null
,或转换函数返回的 Optional
对象为 null
,那么最终得到的也是为 null
的 Optional
对象。
若只需要对 Optional
对象中的值进行转换,而不需要嵌套的 Optional
,那么使用 map
方法更合适。如果要进行一些操作返回另外一个 Optional
对象,flatMap
方法更合适。
2.6 Stream方法
Optional
的 stream
方法,可以将Optional
对象转换为 Stream
对象,对其中的值进行流操作。
如果 Optional
对象包含值,则将这个值封装到一个 Stream
流中,如果 Optional
对象为空,则创造一个为空的 Stream
流。
java">Stream<String> stream = optionalUser.map(User::getName).stream();
stream.forEach(System.out::println);
⚠️注:stream
方法是 Java 9 引入的。
3.不适合场景
- 不应该用于类的字段,会增加内存消耗,并使序列化变得复杂;
- 不应该用于方法参数,使方法的理解和使用变得复杂;
- 不应用于构造器参数,迫使调用者创建 Optional 实例,应该通过构造器重载解决;
- 不应该用于集合的参数,集合已经很好的处理空集合的情况,没必要使用 Optional 包装集合;
- 不建议使用
get
方法,若为null
会报错。建议使用orElseGet
。
4.适用场景
应用场景一般用于方法返回值或者集合操作中。
场景一:
Optional
主要用作返回类型。在获取到这个类型的实例后,如果它有值,你可以取得这个值,否则可以进行一些替代行为。
java">User user = new User("shwen", null);
String fullName = Optional.ofNullable(user.getFullName()).orElse("未知名称");
场景二:
在对集合进行操作时,某些操作可能返回 null(例如在查找元素时)。使用 Optional
可以更好地处理这些情况,而不是直接返回 null
java">public void whenEmptyStream_thenReturnDefaultOptional() {List<User> users = new ArrayList<>();User user = users.stream().findFirst().orElse(new User("default", "1234"));assertEquals(user.getEmail(), "default");
}
场景三:
设计 API 时,如果某个方法的返回值可能为空,使用 Optional
可以使 API 更加健壮和易于理解。
java">public Optional<Product> findProductById(String productId) {// 根据产品ID查找产品,产品可能不存在// 使用 Optional 封装返回值// 返回 Optional.ofNullable(product) 或 Optional.empty()
}
场景四:
当处理多层嵌套的对象时,使用 Optional
可以避免深层次的 null 检查,使代码更加简洁。
java">public class UserInfo {private String name;private Integer age;private Address address;// 省略构造函数和getter/setter
}public class Address {private String city;// 省略构造函数和getter/setter
}public class OptionalTest {public static void main(String[] args) {UserInfo userInfo = new UserInfo();userInfo.setName("张三");userInfo.setAge(25);userInfo.setAddress(null);String cityName = Optional.ofNullable(userInfo.getAddress()).map(Address::getCity).orElse("未知城市");System.out.println(cityName);}
}输出:
未知城市
本例先通过 Optional.ofNullable
获取 Address
信息,返回类型是 Optional<Address>
。再通过 map
方法获取城市名称。由于 address
字段为 null
,因此 getCity()
方法返回的 Optional<String>
也是空的。最后,通过 orElse
方法返回默认值 “未知城市”。
5.小结
Optional
提供了一系列方法来判断值是否为空、获取值、通过 Lambda
表达式来处理值等操作,使得代码更加健壮和可读性更好。通过使用 Optional
,开发者可以更加清晰地表达值的可能为空的情况,并采取相应的处理措施,有效避免了空指针异常的发生。
⚠️注: Java 9 为 Optional
类添加了三个方法:or()
、ifPresentOrElse()
和 stream()
。
最后提一嘴,Optional
虽然可以提高代码的健壮性,但需要注意不要滥用。主要缺点如下:
- 造成代码冗长,本来一个null值就可以解决
- 引入多一个对象多一个开销,如果本身追求速度,就不用刻意这样
- 如果嵌套多层对象还这么使用,增加代码维护难度
- 对新手不友好
📖Optional实战案例