水下功夫做透,水上才能顺风顺水。

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的优点

  1. 避免空指针异常:使用Optional类可以避免空指针异常,提高代码的健壮性。
  2. 优雅的代码处理:使用Optional类可以使代码更加简洁和优雅,减少代码的嵌套层级,提高代码可读性。
  3. 显式地指出变量可能为空:使用Optional类可以明确地指出一个变量可能为空,从而提醒程序员对此进行处理。
  4. 对集合的处理效率更高:使用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类的优势会得到更加充分的发挥。

posted @ 2024-02-03 09:48  北方寒士  阅读(210)  评论(0编辑  收藏  举报