简介
在这个实验中,你将学习在 Java 中处理集合时如何有效地处理空值。集合是基础的数据结构,编写能够安全处理集合变量可能为 null 的健壮代码至关重要。你将探索如何检查集合是否为 null,如何结合 null 和空值检查以进行全面验证,以及如何利用 Optional
类来增强空值安全性,最终帮助你避免常见的 NullPointerException
错误。
在这个实验中,你将学习在 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
或为空的集合的一种非常常见且安全的方法。
在这一步中,我们将探索一种更现代的方法,即使用 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 代码的一项基本技能。