首页 > 编程语言 >Java 8 Optional:优雅处理空指针异常的新型容器

Java 8 Optional:优雅处理空指针异常的新型容器

时间:2024-02-03 09:55:06浏览次数:33  
标签:Java String 对象 out println null 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类的优势会得到更加充分的发挥。

标签:Java,String,对象,out,println,null,Optional,指针
From: https://www.cnblogs.com/shijianchuzhenzhi/p/18004370

相关文章

  • 包机制和JavaDoc
    包机制为了更好地组织类,Java提供包机制,用于区别类名的命名空间。包语句的语法格式为:packagepkg1[.pkg2[.pkg3...]]一般利用公司域名倒置作为包名;//com.hongyi.xxx为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。使用“import”语句可完成此功能。impor......
  • JVM(Java虚拟机)整理(二)
    前言上一篇内容:JVM(Java虚拟机)整理(一)Java内存模型(JMM)Java内存模型引入声明:本节内容转载于@pdai:JVM基础-Java内存模型引入。很多人都无法区分Java内存模型和JVM内存结构,以及Java内存模型与物理内存之间的关系。本文从堆栈角度引入JMM,然后介绍JMM和物理内存之间的关系。@......
  • JAVA8 - 函数式接口
    目录原始类型特化函数描述符原始类型特化JAVA8为Predicate、Consumer、Suppler等函数式接口带来了一个专门的版本,以便在输入和输出时都是基本类型时避免自动装箱的操作IntPredicateevenNumbers=(inti)->i%2==0;//无装箱Predicate<Integer>evenNumbers2=(Inte......
  • [Java]静态代理、动态代理(基于JDK1.8)
    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)https://www.cnblogs.com/cnb-yuchen/p/18002823出自【进步*于辰的博客】参考笔记一,P83;笔记二,P75.4。目录1、静态代理1.1概述1.2静态代理的两种形式1.2.1面向接口1.2.2面向继承2、动态代理2.1什么是动态代......
  • 【Java】SpringBoot集成微信V3支付
    前言这篇文章主要实现一下通过IJPay来实现微信v3支付案例,本篇文章使用的是JSAPI即小程序支付准备工作导入依赖<dependency><groupId>com.github.javen205</groupId><artifactId>IJPay-WxPay</artifactId><version>2.9.6</versio......
  • JAVA数组练习代码
    一维数组的有序插入思路代码点击查看代码importjava.util.Scanner;/***@authorLittleBear*@date2024-02-02-16:57*/publicclassseqInsertion{publicstaticvoidmain(String[]args){System.out.println("pleaseinputyournum:");......
  • java - 判断时间范围区间
    JSONObjectrespObj=newJSONObject(s);SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd");StringstartTimeStr="2024-01-01";StringendTimeStr="2024-01-31";DatestartTimDate=sdf.parse(startTimeStr);//strin......
  • Java 中的泛型机制
    泛型JDK5.0之后推出的新特性:泛型泛型这种语法机制,只在程序编译阶段起作用,只给编译器参考的(运行阶段没用)使用泛型的好处:集合中存储的元素统一了从集合中取出来的元素类型是泛型指定的类型,不需要进行大量的“向下转型”泛型的缺点:集合中的元素缺乏多样性importjava.util.Ar......
  • Java 中的HashSet 和 TreeSet
    HashSetHashSet集合:无序不可重复方法HashSet集合的元素实际上是放到HashMap集合的Key中importjava.util.HashSet;importjava.util.Set;/**HashSet集合:无序不可重复**/publicclassHashSetTest{publicstaticvoidmain(String[]args){//演示一......
  • JAVA的一些冷门知识
    1、@Size可以用来校验数组长度。2、构造代码块,在构造函数之前被调用,静态构造代码块,只被调用一次,有点类似C#的静态构造函数。java中的四种代码块_java代码块的分类-CSDN博客3、匿名内部类是Java编程语言中一种特殊的类,它没有显式地定义类名,而是在创建对象时通过传递实现了某个......