Optional类
0.由来
实际 Java 开发过程中,尝试访问空引用的属性或者调用空引用的方法,会报 空指针异常(NullPointerException)。处理可能为 null 的值时,需要增加很多 条件判定,比如:
💗User:
@Data
@AllArgsConstructor
public class User {String name;String fullName;
}
💙UserRepository:
public class UserRepository {public User findUserByName(String name){if(name.equals("Shwen")){return new User("Shwen","Shwen shen");} else {return null;}}
}
👉OptionalTest:
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 异常。
Optional.of(new User("Shwen","Shwen shen"));
ofNullable:创建可能为 null 的对象
Optional.ofNullable(new User("Shwen","Shwen shen"));
empty:创建的对象为 null
Optional.empty();
💙改造UserRepository:
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。
boolean flag = optionalUser.isPresent();
2.3 获取方法
get:若取值对象为null,会报NoSuchElementException异常(并非NullPointerException异常)。
User user = optionalUser.get();
orElse:用于获取值或在值为null的情况下提供一个默认值。
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表达式实现。
User user = optionalUser.orElseGet(() -> new User("Stark","Tony Stark"));
📌orElse 和 orElseGet 区别如下:
orElse入参是对象,orElseGet入参是Supplier的函数式接口orElse即使获取的数据不为null,仍会创建一个新User对象。而orElseGet不会创建。因此,最好使用只有读取的数据为null的时候才会新建对象的orElseGet方法。
orElseThrow:用于在Optional对象中的值为null时抛出一个指定的异常。
User user = optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
2.4 判断获取方法
ifPresent:参数中若对象不为null,则会执行Labmda中的方法;若参数对象为null,则不会执行Labmda中的方法,也不会报错。
optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
ifPresentOrElse:当我们希望值为null时进行其他操作,需要使用ifPresentOrElse方法。⚠️注:ifPresentOrElse方法是 Java 9 引入的。
optionalUser.ifPresentOrElse(user -> System.out.println(user.getFullName()),()->System.out.println("User not found"));
filter:若满足filter方法中的条件,则会返回包含值的Optional对象,如果不满足,则返回空的Optional对象。
Optional<User> optionalUser2 = optionalUser.filter(user -> user.getFullName().equals("Shwen shen"));
System.out.println(optionalUser2.isPresent());
2.5 map与flatMap方法
2.5.1 map
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
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
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 流。
Stream<String> stream = optionalUser.map(User::getName).stream();
stream.forEach(System.out::println);
⚠️注:stream 方法是 Java 9 引入的。
3.不适合场景
- 不应该用于类的字段,会增加内存消耗,并使序列化变得复杂;
- 不应该用于方法参数,使方法的理解和使用变得复杂;
- 不应用于构造器参数,迫使调用者创建 Optional 实例,应该通过构造器重载解决;
- 不应该用于集合的参数,集合已经很好的处理空集合的情况,没必要使用 Optional 包装集合;
- 不建议使用
get方法,若为null会报错。建议使用orElseGet。
4.适用场景
应用场景一般用于方法返回值或者集合操作中。
场景一:
Optional 主要用作返回类型。在获取到这个类型的实例后,如果它有值,你可以取得这个值,否则可以进行一些替代行为。
User user = new User("shwen", null);
String fullName = Optional.ofNullable(user.getFullName()).orElse("未知名称");
场景二:
在对集合进行操作时,某些操作可能返回 null(例如在查找元素时)。使用 Optional 可以更好地处理这些情况,而不是直接返回 null
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 更加健壮和易于理解。
public Optional<Product> findProductById(String productId) {// 根据产品ID查找产品,产品可能不存在// 使用 Optional 封装返回值// 返回 Optional.ofNullable(product) 或 Optional.empty()
}
场景四:
当处理多层嵌套的对象时,使用 Optional 可以避免深层次的 null 检查,使代码更加简洁。
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实战案例
