介绍
在这个实验中,你将学习在 Java 中处理集合时如何有效地处理空值。集合是基础的数据结构,编写能够安全处理集合变量可能为 null 的健壮代码至关重要。你将探索如何检查集合是否为 null,如何结合 null 和空值检查以进行全面验证,以及如何利用 Optional 类来增强空值安全性,最终帮助你避免常见的 NullPointerException 错误。
测试集合是否为空
在这一步中,我们将探讨在 Java 中处理集合时如何处理空值。像 List 或 Set 这样的集合是基础的数据结构,编写能够安全处理集合变量可能为 null 的代码至关重要。
在 Java 中,null 值意味着变量不引用任何对象。如果你尝试访问 null 对象的方法或属性,程序将会因 NullPointerException 而崩溃。这是 Java 中非常常见的错误,学习如何避免它至关重要。
让我们先创建一个简单的 Java 程序来演示这个问题。
如果
HelloJava.java文件尚未在 WebIDE 编辑器中打开,请打开它。将文件的全部内容替换为以下代码:
import java.util.List; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Intentionally set to null // This line will cause a NullPointerException if names is null // int size = names.size(); // System.out.println("Size of the list: " + size); System.out.println("Program finished."); } }在这段代码中,我们声明了一个名为
names的字符串列表,并明确将其设置为null。注释掉的代码行展示了如果我们尝试对null列表调用size()方法会发生什么 —— 这将导致NullPointerException。保存文件(Ctrl+S 或 Cmd+S)。
现在,让我们编译这个程序。打开 WebIDE 底部的终端,并确保你位于
~/project目录下。运行以下命令:javac HelloJava.java如果编译成功,你应该看不到任何输出。
现在,运行这个程序:
java HelloJava你应该会看到以下输出:
Program finished.由于会导致
NullPointerException的代码行被注释掉了,程序运行时不会崩溃。
现在,让我们修改代码,在使用集合之前检查其是否为 null。
再次在编辑器中打开
HelloJava.java。修改
main方法以包含空值检查:import java.util.List; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Intentionally set to null if (names != null) { // This code will only run if names is NOT null int size = names.size(); System.out.println("Size of the list: " + size); } else { System.out.println("The list is null."); } System.out.println("Program finished."); } }我们添加了一个
if语句来检查names是否不等于null(names != null)。获取列表大小并打印的代码现在位于这个if块内,这意味着只有当names是一个有效的列表对象时,该代码才会执行。else块则处理names为null的情况。保存文件。
编译修改后的程序:
javac HelloJava.java再次运行程序:
java HelloJava这次,你应该会看到以下输出:
The list is null. Program finished.程序正确识别出列表为
null并打印了相应的消息,避免了NullPointerException。
这个简单的 if (collection != null) 检查是处理集合时防止 NullPointerException 的最基本方法。这是你在 Java 编程中会经常使用的基础技术。
结合空值和空集合检查
在上一步中,我们学习了如何检查集合是否为 null。然而,即使集合不为 null,它也可能是 空的(不包含任何元素)。在很多情况下,你可能希望以相同的方式处理 null 集合和空集合,或者至少处理这两种可能性。
检查集合是否为空可以使用 isEmpty() 方法。如果集合不包含任何元素,该方法返回 true,否则返回 false。
让我们修改程序来演示 null 列表和空列表之间的区别,然后将检查结合起来。
在 WebIDE 编辑器中打开
HelloJava.java文件。将文件内容替换为以下代码:
import java.util.List; import java.util.ArrayList; // Import ArrayList public class HelloJava { public static void main(String[] args) { List<String> nullList = null; // Intentionally set to null List<String> emptyList = new ArrayList<>(); // An empty list System.out.println("Checking nullList:"); if (nullList != null) { System.out.println("nullList is not null."); if (nullList.isEmpty()) { System.out.println("nullList is empty."); } else { System.out.println("nullList is not empty."); } } else { System.out.println("nullList is null."); } System.out.println("\nChecking emptyList:"); if (emptyList != null) { System.out.println("emptyList is not null."); if (emptyList.isEmpty()) { System.out.println("emptyList is empty."); } else { System.out.println("emptyList is not empty."); } } else { System.out.println("emptyList is null."); } System.out.println("\nProgram finished."); } }我们添加了一个
emptyList,它被初始化为一个新的空ArrayList。然后,我们对nullList和emptyList执行相同的空值和空集合检查,以查看输出的差异。保存文件。
在终端中编译程序:
javac HelloJava.java运行程序:
java HelloJava你应该会看到类似以下的输出:
Checking nullList: nullList is null. Checking emptyList: emptyList is not null. emptyList is empty. Program finished.这个输出清楚地表明,
nullList是null,而emptyList不为null但为空。
现在,让我们将空值和空集合检查合并为一个条件。常见的做法是检查集合是否为 null 或为空。
在编辑器中打开
HelloJava.java。修改
main方法以合并检查:import java.util.List; import java.util.ArrayList; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Can be null or an empty list // Combined check: is names null OR is names empty? if (names == null || names.isEmpty()) { System.out.println("The list is null or empty."); } else { System.out.println("The list is not null and not empty."); // You can safely iterate or access elements here // For example: // System.out.println("First element: " + names.get(0)); } // Let's test with an empty list List<String> anotherList = new ArrayList<>(); System.out.println("\nChecking anotherList (empty):"); if (anotherList == null || anotherList.isEmpty()) { System.out.println("anotherList is null or empty."); } else { System.out.println("anotherList is not null and not empty."); } // Let's test with a non-empty list List<String> populatedList = new ArrayList<>(); populatedList.add("Item 1"); System.out.println("\nChecking populatedList (not empty):"); if (populatedList == null || populatedList.isEmpty()) { System.out.println("populatedList is null or empty."); } else { System.out.println("populatedList is not null and not empty."); } System.out.println("\nProgram finished."); } }我们使用逻辑或运算符 (
||) 来合并条件names == null和names.isEmpty()。如果 任一 条件为真,if块将执行。我们还添加了对空列表和非空列表的测试,以查看合并检查的行为。重要提示:
names == null || names.isEmpty()中条件的顺序至关重要。如果names为null,条件的第一部分 (names == null) 为真。由于 Java 中的短路求值,第二部分 (names.isEmpty()) 不会 被求值,从而避免了NullPointerException。如果你写成names.isEmpty() || names == null,并且names为null,调用names.isEmpty()会导致NullPointerException。在与对象的其他检查结合时,始终要 先 检查null。保存文件。
编译程序:
javac HelloJava.java运行程序:
java HelloJava你应该会看到类似以下的输出:
The list is null or empty. Checking anotherList (empty): anotherList is null or empty. Checking populatedList (not empty): populatedList is not null and not empty. Program finished.这展示了合并检查如何正确识别
null和空列表,并将它们与非空列表区分开来。这种合并检查是处理可能为null或为空的集合的一种非常常见且安全的方法。
使用 Optional 实现空值安全
在这一步中,我们将探索一种更现代的方法,即使用 Java 8 引入的 Optional 类来处理 Java 中可能出现的 null 值。Optional 是一个容器对象,它可能包含也可能不包含非 null 值。它提供了一种更明确地表示值是否存在的方式,有助于降低 NullPointerException 的风险。
虽然 if (collection != null) 检查在许多情况下是完全有效的且必要的,但 Optional 可以使你的代码更具可读性和表现力,特别是在处理可能返回值或可能返回 null 的方法时。
让我们看看如何将 Optional 与集合一起使用。尽管 Optional 通常用于单个值,但你可能会遇到方法返回 Optional<List<SomeObject>> 的情况。
在 WebIDE 编辑器中打开
HelloJava.java文件。将文件内容替换为以下代码,该代码演示了如何将
Optional与可能为null的列表一起使用:import java.util.List; import java.util.ArrayList; import java.util.Optional; // Import Optional public class HelloJava { // A method that might return an Optional containing a list, or an empty Optional public static Optional<List<String>> getNames(boolean includeNames) { if (includeNames) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); return Optional.of(names); // Return an Optional containing the list } else { return Optional.empty(); // Return an empty Optional } } public static void main(String[] args) { // Case 1: Get names when includeNames is true Optional<List<String>> namesOptional1 = getNames(true); System.out.println("Checking namesOptional1:"); // Check if the Optional contains a value if (namesOptional1.isPresent()) { List<String> names = namesOptional1.get(); // Get the list from the Optional System.out.println("List is present. Size: " + names.size()); // You can also check if the list itself is empty if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } } else { System.out.println("List is not present (Optional is empty)."); } System.out.println("---"); // Case 2: Get names when includeNames is false Optional<List<String>> namesOptional2 = getNames(false); System.out.println("Checking namesOptional2:"); if (namesOptional2.isPresent()) { List<String> names = namesOptional2.get(); System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } } else { System.out.println("List is not present (Optional is empty)."); } System.out.println("\nProgram finished."); } }在这段代码中:
- 我们定义了一个
getNames方法,它返回一个Optional<List<String>>。这个方法模拟了一种场景,即你可能会得到一个列表,也可能什么都得不到(由一个空的Optional表示)。 - 在
main方法中,我们分别传入true和false调用getNames方法来测试这两种情况。 - 我们使用
namesOptional.isPresent()来检查Optional是否包含列表。 - 如果
isPresent()返回true,我们使用namesOptional.get()来获取列表。这是安全的,因为我们已经检查过值是否存在。 - 在
isPresent()代码块内,我们可以对列表本身进行检查,例如names.isEmpty()。
- 我们定义了一个
保存文件。
在终端中编译程序:
javac HelloJava.java运行程序:
java HelloJava你应该会看到类似以下的输出:
Checking namesOptional1: List is present. Size: 2 List is not empty. First name: Alice --- Checking namesOptional2: List is not present (Optional is empty). Program finished.这个输出展示了
Optional如何帮助我们处理可能根本不返回列表的情况。
Optional 还提供了其他有用的方法来处理值不存在的情况,例如:
orElse(defaultValue):如果值存在则返回该值,否则返回默认值。orElseGet(supplier):如果值存在则返回该值,否则返回supplier函数的结果。orElseThrow(exceptionSupplier):如果值存在则返回该值,否则抛出exceptionSupplier产生的异常。ifPresent(consumer):如果值存在,则执行给定的操作。
让我们修改代码,使用 ifPresent 以更简洁的方式处理列表存在的情况。
在编辑器中打开
HelloJava.java。修改
main方法以使用ifPresent:import java.util.List; import java.util.ArrayList; import java.util.Optional; public class HelloJava { public static Optional<List<String>> getNames(boolean includeNames) { if (includeNames) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); return Optional.of(names); } else { return Optional.empty(); } } public static void main(String[] args) { // Case 1: Get names when includeNames is true Optional<List<String>> namesOptional1 = getNames(true); System.out.println("Checking namesOptional1 using ifPresent:"); namesOptional1.ifPresent(names -> { // This block only runs if namesOptional1 contains a list System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } }); if (!namesOptional1.isPresent()) { // Still need a check if you need to handle the absence case System.out.println("List is not present (Optional is empty)."); } System.out.println("---"); // Case 2: Get names when includeNames is false Optional<List<String>> namesOptional2 = getNames(false); System.out.println("Checking namesOptional2 using ifPresent:"); namesOptional2.ifPresent(names -> { System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } }); if (!namesOptional2.isPresent()) { System.out.println("List is not present (Optional is empty)."); } System.out.println("\nProgram finished."); } }我们将
if (namesOptional.isPresent()) { ... namesOptional.get() ... }结构替换为namesOptional.ifPresent(names -> { ... })。lambda 表达式 (names -> { ... }) 内的代码仅在Optional包含值时执行。我们仍然添加了if (!namesOptional.isPresent())检查来处理Optional为空的情况,因为ifPresent仅处理值存在的情况。保存文件。
编译程序:
javac HelloJava.java运行程序:
java HelloJava输出应该与之前相同,这表明
ifPresent提供了另一种处理Optional中值存在情况的方式。
使用 Optional 可以使你的代码在值是否可能缺失方面的意图更加清晰,并鼓励你显式地处理这种缺失情况,从而降低意外出现 NullPointerException 的可能性。
总结
在本次实验中,我们学习了如何在 Java 中处理空集合,以防止出现 NullPointerException。我们首先展示了对空集合调用方法会导致运行时错误的问题。
接着,我们探索了不同的技术,以便在尝试访问集合的元素或属性之前,安全地检查集合是否为空。这是编写健壮且无错误的 Java 代码的一项基本技能。



