Java 8 Optional:优雅处理空指针异常的新型容器
一、Optional概述
1.1 Optional定义
Optional是Java 8中引入的一个类,用于描述一个值不存在的情况。它可以存储任意类型的值,或者表示一个空值。使用Optional类可以避免null值的传递和检查,提高代码的健壮性和可读性。
Optional类的定义如下:
public final class Optional<T> {
// ...
}
上述代码中,Optional类是一个泛型类,可以存储任意类型的单个值。它是不可变的,线程安全的。
Optional类有以下方法:
- empty():返回一个空的Optional对象。
- of(T value):创建一个包含非null值的Optional对象。如果值为null,则会抛出NullPointerException异常。
- ofNullable(T value):创建一个包含指定值的Optional对象。如果该值为null,则创建一个空的Optional对象。
- get():获取Optional对象中的值。如果Optional对象为空,则会抛出NoSuchElementException异常。
- isPresent():判断Optional对象是否包含值。
- ifPresent(Consumer<? super T> action):如果Optional对象包含值,则对该值执行指定的操作。
- orElse(T other):如果Optional对象不包含值,则返回指定的默认值。
- orElseGet(Supplier<? extends T> other):如果Optional对象不包含值,则返回由指定的Supplier生成的默认值。
- orElseThrow(Supplier<? extends X> exceptionSupplier):如果Optional对象不包含值,则抛出由指定的Supplier产生的异常。
1.2 Optional属性
- 在Java 8中,Optional类有以下属性:
private static final Optional<?> EMPTY
- 表示空的Optional对象,是只读的,不可修改。private final T value
- 存储Optional对象的值,如果Optional对象为空,则该值为null。
这两个属性都是私有的,因此无法从外部直接访问。需要使用Optional类提供的方法来访问或操作它们。
另外需要注意的是,Optional类的value属性被声明为final,这意味着一旦Optional对象中的值被设置,就无法再修改。这样可以确保Optional对象的不可变性,从而提高代码的健壮性和可读性。
二、创建Optional对象
2.1 创建空的Optional
可以使用静态方法empty()来创建一个空的Optional对象。
Optional<Object> emptyOptional = Optional.empty();
System.out.println(emptyOptional); // 输出Optional.empty
2.2 创建非空的Optional
可以使用静态方法of()或ofNullable()来创建一个非空的Optional对象。
2.2.1 of()
使用of()方法创建一个非空的Optional对象,如果参数为null,则会抛出NullPointerException异常。(避免使用)
String value = "Hello World";
Optional<String> optionalStr = Optional.of(value);
System.out.println(optionalStr); // 输出Optional[Hello World]
String nullValue = null;
Optional<String> optionalNull = Optional.of(nullValue); // 抛出NullPointerException
2.2.2 ofNullable()
使用ofNullable()方法创建一个Optional对象,如果参数为null,则返回一个空Optional对象。(推荐使用)
String value = "Hello World";
Optional<String> optionalStr = Optional.ofNullable(value);
System.out.println(optionalStr); // 输出Optional[Hello World]
String nullValue = null;
Optional<String> optionalNull = Optional.ofNullable(nullValue);
System.out.println(optionalNull); // 输出Optional.empty
三、Optional的使用方法
3.1 检查Optional是否有值
可以使用isPresent()方法检查Optional是否有值,返回一个boolean类型的值。
Optional<String> optionalStr = Optional.of("Hello World");
if (optionalStr.isPresent()) {
System.out.println("optionalStr存在值:" + optionalStr.get());
} else {
System.out.println("optionalStr不存在值");
}
3.2 获取Optional中的值
可以使用get()方法获取Optional对象中的值,如果Optional为空,则会抛出NoSuchElementException异常。(避免使用)
Optional<String> optionalStr = Optional.of("Hello World");
String value = optionalStr.get();
System.out.println(value); // 输出Hello World
Optional<String> optionalNull = Optional.empty();
String nullValue = optionalNull.get(); // 抛出NoSuchElementException异常
3.3 替换或使用默认值
可以使用orElse()和orElseGet()方法替换Optional对象中的值或使用默认值。
3.3.1 orElse()
使用orElse()方法可以设置一个默认值,如果Optional对象中的值为空,则返回默认值。(空值时,直接设置默认值,推荐)
Optional<String> optionalStr = Optional.of("Hello World");
String value = optionalStr.orElse("Default Value");
System.out.println(value); // 输出Hello World
Optional<String> optionalNull = Optional.empty();
String nullValue = optionalNull.orElse("Default Value");
System.out.println(nullValue); // 输出默认值 Default Value
3.3.2 orElseGet()
使用orElseGet()方法可以设置一个Supplier接口实现,返回一个默认值,如果Optional对象中的值为空,
则调用Supplier接口实现获取默认值。(空值时,通过接口设置默认值,推荐)
Optional<String> optionalStr = Optional.of("Hello World");
String value = optionalStr.orElseGet(() -> "Default Value");
System.out.println(value); // 输出Hello World
Optional<String> optionalNull = Optional.empty();
String nullValue = optionalNull.orElseGet(() -> "Default Value");
System.out.println(nullValue); // 输出默认值 Default Value
3.4 Optional的链式调用
可以使用map()、flatMap()和filter()等方法进行链式调用,操作Optional的值。其中map()和flatMap()可以对Optional中的值进行映射或转换操作,filter()可以进行过滤操作。
3.4.1 map()
使用map()方法可以对Optional对象中的值进行映射或转换操作,返回一个新的Optional对象。
Optional<String> optionalStr = Optional.of("Hello World");
Optional<Integer> optionalLength =
optionalStr.map(String::length);
System.out.println(optionalLength.get()); // 输出11
Optional<String> optionalNull = Optional.empty();
Optional<Integer> optionalLengthNull =
optionalNull.map(String::length);
System.out.println(optionalLengthNull.isPresent()); // 输出false
3.4.2 flatMap()
使用flatMap()方法可以对Optional对象中的值进行映射或转换操作,返回一个新的Optional对象,与map()方法不同的是,flatMap()方法返回的是一个Optional对象,而map()方法返回的是一个包含Optional对象的Optional对象。
Optional<String> optionalStr = Optional.of("Hello World");
Optional<Integer> optionalLength =
optionalStr.flatMap(str -> Optional.of(str.length()));
System.out.println(optionalLength.get()); // 输出11
Optional<String> optionalNull = Optional.empty();
Optional<Integer> optionalLengthNull =
optionalNull.flatMap(str -> Optional.of(str.length()));
System.out.println(optionalLengthNull.isPresent()); // 输出false
3.4.3 filter()
使用filter()方法可以对Optional对象中的值进行过滤操作,返回一个新的Optional对象。如果Optional对象中的值不满足谓词条件,则返回一个空的Optional对象。
Optional<String> optionalStr = Optional.of("Hello World");
Optional<String> optionalFiltered =
optionalStr.filter(str -> str.contains("H"));
System.out.println(optionalFiltered.get()); // 输出Hello World
Optional<String> optionalNull = Optional.empty();
Optional<String> optionalFilteredNull =
optionalNull.filter(str -> str.contains("H"));
System.out.println(optionalFilteredNull.isPresent()); // 输出false
3.5 Optional.flatMap方法
使用flatMap方法,可以避免链式调用中出现嵌套Optional对象的情况。flatMap方法接受一个Function类型的参数,该函数将Optional中的值映射为另一个Optional。
public class Person {
private String name;
private Optional<Address> address;
public Person(String name, Optional<Address> address) {
this.name = name;
this.address = address;
}
public Optional<Address> getAddress() {
return address;
}
}
public class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
}
Optional<Person> optionalPerson =
Optional.of(new Person("Jack", Optional.of(new Address("Beijing"))));
String city = optionalPerson.flatMap(Person::getAddress)
.map(Address::getCity)
.orElse("Unknown");
System.out.println(city); // 输出Beijing
3.6 Optional.filter方法
使用filter()方法可以过滤Optional中的值。如果Optional中的值满足谓词条件,返回一个包含该值的Optional,否则返回一个空Optional。
Optional<Integer> optionalInt = Optional.of(10);
Optional<Integer> filtered = optionalInt.filter(x -> x > 5);
System.out.println(filtered.get()); // 输出10
Optional<Integer> emptyFiltered = optionalInt.filter(x -> x > 20);
System.out.println(emptyFiltered.isPresent()); // 输出false
3.7 Optional.isPresent方法
使用isPresent()方法可以检查Optional对象中是否存在值。
Optional<Integer> optionalInt = Optional.of(10);
if (optionalInt.isPresent()) {
System.out.println(optionalInt.get()); // 输出10
}
Optional<Integer> emptyOptional = Optional.empty();
if (emptyOptional.isPresent()) {
System.out.println(emptyOptional.get());
} else {
System.out.println("emptyOptional对象为空");
}
3.8 Optional.ifPresent方法
使用ifPresent()方法可以避免代码出现null的判断,如果Optional对象中存在值,则执行指定的操作。
Optional<Integer> optionalInt = Optional.of(10);
optionalInt.ifPresent(x -> System.out.println(x)); // 输出10
Optional<Integer> emptyOptional = Optional.empty();
emptyOptional.ifPresent(x -> System.out.println(x)); // 没有输出
四、Optional的优点和应用场景
4.1 Optional的优点
- 避免空指针异常:使用Optional类可以避免空指针异常,提高代码的健壮性。
- 优雅的代码处理:使用Optional类可以使代码更加简洁和优雅,减少代码的嵌套层级,提高代码可读性。
- 显式地指出变量可能为空:使用Optional类可以明确地指出一个变量可能为空,从而提醒程序员对此进行处理。
- 对集合的处理效率更高:使用Optional类对集合进行处理,可以避免遍历整个集合,从而提高处理效率。
4.2 Optional在集合处理中的应用
在集合处理中,Optional类被广泛应用,可以有效地提高代码的健壮性和可读性。
下面是一个使用Optional类过滤集合中的null值的示例:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ListUtils {
public static List<String> filterNullValues(List<String> list) {
return list.stream()
.filter(str -> str != null)
.map(Optional::ofNullable)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
}
上述代码中,ListUtils.filterNullValues(List list)方法会过滤输入的List对象中的null值,并返回一个新的List对象。过程中使用了Java 8的Stream API 和 Optional类。
下面是对上述代码的解释:
- 通过list.stream()方法将输入的List对象转换成一个Stream对象。
- 使用filter()函数过滤掉值为null的元素。
- 使用map()函数将不为null的元素包装成Optional对象。
- 使用filter()函数过滤掉没有值的Optional对象。
- 使用map()函数获取Optional对象中的值。
- 最后使用collect()函数将过滤过后的元素收集起来,并返回一个新的List对象。
使用Optional类可以避免null值的传递和检查,减少代码的嵌套层级,提高代码可读性。调用ListUtils.filterNullValues(List list)方法的代码可以这样写:
List<String> list = Arrays.asList("hello", null, "world", null, "java");
List<String> filteredList = ListUtils.filterNullValues(list);
// 输出:[hello, world, java]
System.out.println(filteredList);
上述代码中,将一个包含null值的List对象传入filterNullValues方法中,并输出过滤后得到的新的List对象。
4.3 Optional在函数式编程中的应用
在函数式编程中,Optional类被广泛应用,可以有效地避免空指针异常,减少代码的嵌套层级,提高代码可读性。
下面是一个使用Optional类处理null值的示例:
import java.util.Optional;
public class StringUtils {
public static Optional<String> reverse(String s) {
return (s == null || s.isEmpty()) ? Optional.empty() : Optional.of(new StringBuilder(s).reverse().toString());
}
}
上述代码中,StringUtils.reverse(String s)方法会将输入的字符串反转,并返回一个Optional对象。如果输入字符串为null或空串,就会返回一个空的Optional对象。
下面是对上述代码的解释:
- 在三目运算符中,判断输入的字符串是否为null或空串。
- 如果输入的字符串为null或空串,则返回一个空的Optional对象。
- 如果输入的字符串不为null且不为空串,则使用StringBuilder对其进行反转,并将得到的结果封装成一个Optional对象返回。
使用Optional类可以避免null值的传递和检查,避免空指针异常。调用StringUtils.reverse(String s)方法的代码可以这样写:
Optional<String> reversedStringOptional = StringUtils.reverse("hello, world!");
if (reversedStringOptional.isPresent()) {
String reversedString = reversedStringOptional.get();
// ...
} else {
// 输入的字符串为null或空串
}
上述代码中,如果reverse方法返回的Optional对象不为空,就可以使用get()方法获取Optional对象里面的值。如果reverse方法返回的Optional对象为空,就表示输入的字符串为null或空串。
4.4 Optional在I/O操作中的应用
在I/O操作中,Optional类可以用于处理读取文件时遇到的空行或者空值的情况,从而提高程序的健壮性。
下面是一个使用Optional类处理空行和空值的示例:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Optional;
public class FileUtils {
public static Optional<String> readLine(String filePath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line = null;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
return Optional.of(line);
}
}
return Optional.empty();
}
}
}
上述代码中,FileUtils.readLine(String filePath)方法会读取指定路径下的文件,并返回其中第一个不为空的行。如果文件为空、文件不存在、或者文件中所有行都为空,方法就会返回一个空的Optional对象。
下面是对上述代码的解释:
- 在try语句块中,将要被读取的文件通过Reader对象(reader)传入BufferedReader构造函数中。
- 初始化line为null。
- 当Reader对象成功读取到一行并且该行不为空时,使用Optional的of方法将当前行封装成一个Optional对象并返回。
- 如果逐行读取文件后,所有行都为空,则返回一个空的Optional对象。
- 在try语句块退出时,会自动关闭Reader对象。
使用Optional类可以避免空指针异常,提高代码的健壮性和可读性。调用FileUtils.readLine(String filePath)方法的代码可以这样写:
Optional<String> lineOptional = FileUtils.readLine("path/to/file");
if (lineOptional.isPresent()) {
String line = lineOptional.get();
// ...
} else {
// 文件为空或不存在
}
上述代码中,如果readLine方法返回的Optional对象不为空,就可以使用get()方法获取Optional对象里面的值。如果readLine方法返回的Optional对象为空,就表示文件为空或不存在。
4.5 Optional在Spring框架中的应用
在Spring框架中,Optional类经常用于处理可能为空的返回值。下面是一些Optional在Spring框架中的应用场景:
1. Controller层接口返回值
在Controller层处理请求时,可以使用Optional类来封装接口的返回值,以便更好地处理可能为空的情况。例如:
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> userOptional = userService.getUserById(id);
return userOptional.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
上述代码中,如果userService.getUserById(id)方法返回的Optional对象不为空,map()方法将会将其转换成一个HttpStatus为200的ResponseEntity返回;如果返回的Optional对象为空,orElse()方法将会返回一个HttpStatus为404的ResponseEntity。
2. Service层操作数据库返回值
在Service层操作数据库查询时,经常需要根据查询结果的空值情况进行不同的处理。使用Optional类可以更好地处理这种情况。例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
}
上述代码中,getUserById方法返回的Optional对象包含了根据id从数据库中查询到的User对象。因为查询结果可能为空,所以使用Optional类来封装返回值。在Controller层调用这个方法时,可以根据返回的Optional对象是否为空来进行不同的处理。
3. 配置参数的默认值
在Spring Boot项目中,可以使用Java Config方式来配置参数。如果某个参数没有在配置文件中配置,可以在Java Config中为其设置一个默认值。例如:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar().orElse(new DefaultBar()));
}
@Bean
public Optional<Bar> bar() {
// 从配置文件中获取bar的值
String barValue = environment.getProperty("bar");
// 如果bar的值不存在,返回空的Optional对象
if (StringUtils.isBlank(barValue)) {
return Optional.empty();
}
// 如果bar的值存在,返回包含这个值的Optional对象
return Optional.of(new Bar(barValue));
}
}
上述代码中,如果配置文件中没有配置"bar"这个参数,bar()方法会返回一个空的Optional对象,因为没有查询到有效的配置参数。在Foo的构造方法中,如果bar的Optional对象为空,就会使用DefaultBar作为默认值。
五、总结与展望
5.1 Optional的总结
Optional是Java 8中引入的一个新特性,可以避免空指针异常,提高代码的可读性和健壮性,可以在集合处理、函数式编程、I/O操作等场景下发挥重要作用。使用Optional时需要注意值是否存在,避免出现NoSuchElementException异常。
5.2 Optional的发展趋势
随着函数式编程的流行和Java 9、Java 10的发布,Optional类也会得到更多的应用和完善。在Java9中,Optional类新增了ifPresentOrElse()方法,可以在Optional对象为空时执行一个指定的操作。在Java10中,Optional类新增了or()和stream()方法,or()方法可以设置一个备选值,如果Optional对象中不存在值,则返回备选值;stream()方法可以将Optional对象转换为一个包含0或1个元素的Stream流。同时,在函数式编程中,对于空值的处理也越来越重要,Optional类的优势会得到更加充分的发挥。