Java 中移除空数组、null 引用及空集合的终极指南:Stream API 与常见策略详解287


在 Java 编程中,处理集合和数组是日常任务,但数据往往并非总是“干净”的。我们经常会遇到包含 null 引用、长度为零的空数组,甚至内部为空的集合。这些“空”数据不仅会浪费存储空间,还可能导致程序逻辑混乱,甚至抛出 `NullPointerException`。因此,学会高效、优雅地移除这些空数组、null 引用及空集合,是每位 Java 开发者必备的技能。

本文将深入探讨在 Java 中如何识别和移除这些“空”数据。我们将从传统的迭代方法讲起,逐步过渡到现代 Java 的 Stream API,并讨论不同场景下的最佳实践、性能考量以及如何构建通用的解决方案。

一、理解“空”的定义:数组、引用与集合

在着手移除之前,我们首先要明确在 Java 中“空”的几种不同含义:
Null 引用 (Null Reference):这是指一个变量没有指向任何对象。例如,`String[] arr = null;` 表示 `arr` 这个引用本身是空的,它不指向任何数组对象。对 `null` 引用进行任何操作(除了赋值或与 `null` 比较)都将导致 `NullPointerException`。
空数组 (Empty Array):这是指一个实际存在的数组对象,但它的长度为零。例如,`String[] arr = new String[0];` 表示 `arr` 指向一个有效的数组对象,但这个数组不包含任何元素。你可以安全地访问 ``,结果将是 `0`。
包含 Null 元素的数组 (Array with Null Elements):这是指一个非空数组,但其内部某些或所有元素是 `null`。例如,`String[] arr = {"hello", null, "world"};`。这类情况通常在处理数组内部元素时需要特殊考虑。
空集合 (Empty Collection):对于 `List`、`Set` 等集合类型,`()` 返回 `true` 或 `()` 返回 `0`,表示集合中不包含任何元素。例如,`List list = new ArrayList();`。与空数组类似,集合本身是存在的,只是没有内容。
包含 Null 集合引用的集合 (Collection with Null Collection References):一个集合中包含了 `null` 的集合引用。例如,`List listOfLists = new ArrayList(); (null);`。
包含空集合的集合 (Collection with Empty Collections):一个集合中包含了内部为空的集合。例如,`List listOfLists = new ArrayList(); (new ArrayList());`。

本文主要关注前两种(针对数组)以及后三种(针对嵌套集合)情况的移除。

二、传统迭代方法:清晰直观的基础策略

对于初学者或对 Stream API 不熟悉的开发者来说,使用传统的 `for` 循环结合 `List` 来过滤数据是一种直接且易于理解的方法。其核心思想是遍历原始数据结构,将符合条件的元素添加到一个新的列表中。

2.1 移除 `null` 引用和空数组 (针对 `T[][]` 或 `List`)



import ;
import ;
import ;
public class TraditionalRemover {
/
* 移除二维数组中的 null 引用或空数组
*
* @param sourceArray 原始的二维数组,可能包含 null 引用和空数组
* @param <T> 数组元素的类型
* @return 包含非 null 且非空数组的列表
*/
public static <T> List<T[]> removeNullAndEmptyArrays(T[][] sourceArray) {
if (sourceArray == null) {
return new ArrayList<>(); // 或者抛出 IllegalArgumentException
}
List<T[]> resultList = new ArrayList<>();
for (T[] innerArray : sourceArray) {
// 过滤条件:1. innerArray 本身不能是 null;2. innerArray 的长度必须大于 0
if (innerArray != null && > 0) {
(innerArray);
}
}
return resultList;
}
public static void main(String[] args) {
String[][] data = {
{"Apple", "Banana"},
{}, // 空数组
null, // null 引用
{"Orange"},
new String[0], // 另一个空数组
{"Grape", "Melon", "Mango"}
};
List<String[]> filteredList = removeNullAndEmptyArrays(data);
("传统方法移除结果:");
for (String[] arr : filteredList) {
((arr));
}
// 转换为二维数组(如果需要)
String[][] finalArray = (new String[0][]);
("转换为数组:");
for (String[] arr : finalArray) {
((arr));
}
}
}

优点:
直观易懂: 代码逻辑与我们的大脑思维方式一致,一步步地检查和添加。
性能稳定: 对于小到中等规模的数据集,其性能通常足够好,没有额外的开销。

缺点:
代码冗长: 相较于现代 Stream API,需要更多的样板代码。
可读性: 对于复杂的多重过滤条件,循环嵌套可能导致代码难以阅读。
新列表创建: 总是创建一个新的 `List`,这在某些内存敏感的场景下可能需要考虑。

2.2 移除 `null` 集合和空集合 (针对 `List`)


类似地,移除嵌套集合中的 `null` 集合引用或空集合,也可以采用传统迭代方法:
import ;
import ;
public class TraditionalCollectionRemover {
public static <T> List<List<T>> removeNullAndEmptyCollections(List<List<T>> sourceListOfLists) {
if (sourceListOfLists == null) {
return new ArrayList<>();
}
List<List<T>> resultList = new ArrayList<>();
for (List<T> innerList : sourceListOfLists) {
// 过滤条件:1. innerList 本身不能是 null;2. innerList 必须非空 (() 返回 false)
if (innerList != null && !()) {
(innerList);
}
}
return resultList;
}
public static void main(String[] args) {
List<List<String>> data = new ArrayList<>();
(("A", "B"));
(new ArrayList<>()); // 空集合
(null); // null 集合引用
(("C"));
(new ArrayList>()); // 另一个空集合
List<List<String>> filteredList = removeNullAndEmptyCollections(data);
("传统方法移除集合结果:");
for (List<String> list : filteredList) {
(list);
}
}
}

三、Java Stream API:现代、高效且优雅的解决方案

Java 8 引入的 Stream API 彻底改变了集合处理的方式,它提供了一种声明式、函数式的方法来处理数据。对于过滤操作,Stream API 凭借其 `filter()` 方法,能够以更加简洁和富有表达力的方式完成任务。

3.1 移除 `null` 引用和空数组 (针对 `T[][]` 或 `List`)


使用 Stream API 过滤 `null` 引用和空数组,代码会变得非常简洁:
import ;
import ;
import ;
import ;
public class StreamAPIRemover {
/
* 使用 Stream API 移除二维数组中的 null 引用或空数组
*
* @param sourceArray 原始的二维数组
* @param <T> 数组元素的类型
* @return 包含非 null 且非空数组的列表
*/
public static <T> List<T[]> removeNullAndEmptyArraysStream(T[][] sourceArray) {
if (sourceArray == null) {
return (); // 返回一个空的不可变列表
}
return (sourceArray)
.filter(Objects::nonNull) // 过滤掉 null 引用
.filter(arr -> > 0) // 过滤掉空数组
.collect(());
}
/
* 如果输入是 List<T[]>,处理方式类似
*/
public static <T> List<T[]> removeNullAndEmptyArraysFromListStream(List<T[]> sourceList) {
if (sourceList == null) {
return ();
}
return ()
.filter(Objects::nonNull)
.filter(arr -> > 0)
.collect(());
}
public static void main(String[] args) {
String[][] data = {
{"Apple", "Banana"},
{},
null,
{"Orange"},
new String[0],
{"Grape", "Melon", "Mango"}
};
List<String[]> filteredList = removeNullAndEmptyArraysStream(data);
("Stream API 移除结果:");
(arr -> ((arr)));
// 同样可以转换为二维数组
String[][] finalArray = (new String[0][]);
("转换为数组:");
(finalArray).forEach(arr -> ((arr)));
// 针对 List<T[]> 的示例
List<String[]> dataList = new ArrayList<>();
(new String[]{"One"});
(new String[]{});
(null);
(new String[]{"Two", "Three"});
List<String[]> filteredListFromList = removeNullAndEmptyArraysFromListStream(dataList);
("Stream API 移除 List<T[]> 结果:");
(arr -> ((arr)));
}
}

关键点:
`(sourceArray)`: 将数组转换为流。如果输入已经是 `List`,则直接使用 `()`。
`Objects::nonNull`: 这是一个方法引用,等价于 `item -> item != null`。它能够方便地过滤掉流中的 `null` 元素。
`filter(arr -> > 0)`: 这是一个 Lambda 表达式,用于过滤掉长度为零的空数组。
`collect(())`: 将过滤后的流元素收集到一个新的 `List` 中。
`toArray(T[]::new)`: 如果需要将结果转换回数组,可以使用此方法。`new String[0][]` 或 `T[]::new` 作为构造函数引用是最佳实践,它会根据流中的元素数量动态创建合适大小的数组。

3.2 移除 `null` 集合和空集合 (针对 `List`)


对于 `List` 结构,Stream API 的应用同样优雅:
import ;
import ;
import ;
import ;
import ;
public class StreamAPICollectionRemover {
public static <T> List<List<T>> removeNullAndEmptyCollectionsStream(List<List<T>> sourceListOfLists) {
if (sourceListOfLists == null) {
return new ArrayList<>();
}
return ()
.filter(Objects::nonNull) // 过滤掉 null 集合引用
.filter(list -> !()) // 过滤掉空集合 (isEmpty() 方法)
.collect(());
}
public static void main(String[] args) {
List<List<String>> data = new ArrayList<>();
(("A", "B"));
(new ArrayList<>());
(null);
(("C"));
(new ArrayList>());
List<List<String>> filteredList = removeNullAndEmptyCollectionsStream(data);
("Stream API 移除集合结果:");
(::println);
}
}

优点:
简洁性: 代码量显著减少,逻辑表达更清晰。
声明式: 关注“做什么”而不是“怎么做”,提高了代码的可读性。
可并行化: 对于大规模数据集,可以通过 `.parallelStream()` 轻松实现并行处理,提升性能。
函数式: 避免了副作用,提高了代码的健壮性。

缺点:
学习曲线: 对于不熟悉函数式编程的开发者,理解 Stream API 需要一定时间。
调试难度: 链式调用使得调试比传统循环稍复杂。

四、移除数组或集合中的空元素/null 元素 (而非整个数组/集合)

除了移除整个空数组或空集合,我们还经常需要移除数组或集合内部的 `null` 元素或空字符串元素。这通常被称为“数据清洗”的一部分。

4.1 移除数组 `String[]` 中的 `null` 和空字符串



import ;
import ;
import ;
import ;
public class ElementRemover {
/
* 移除 String 数组中的 null 元素和空字符串
* @param sourceArray 原始 String 数组
* @return 包含非 null 且非空字符串的列表
*/
public static List<String> removeNullAndEmptyStrings(String[] sourceArray) {
if (sourceArray == null) {
return new ArrayList<>();
}
return (sourceArray)
.filter(Objects::nonNull) // 过滤 null 字符串引用
.filter(s -> !().isEmpty()) // 过滤空字符串(考虑只有空格的情况)
.collect(());
}
/
* 移除 List<String> 中的 null 元素和空字符串
* @param sourceList 原始 List<String>
* @return 包含非 null 且非空字符串的列表
*/
public static List<String> removeNullAndEmptyStringsFromList(List<String> sourceList) {
if (sourceList == null) {
return new ArrayList<>();
}
return ()
.filter(Objects::nonNull)
.filter(s -> !().isEmpty())
.collect(());
}
public static void main(String[] args) {
String[] data = {"hello", null, "", " ", "world", null, "java"};
List<String> filteredStrings = removeNullAndEmptyStrings(data);
("移除 String 数组中的 null 和空字符串:");
(filteredStrings); // 输出: [hello, world, java]
List<String> dataList = ("apple", null, " ", "banana", "");
List<String> filteredListStrings = removeNullAndEmptyStringsFromList(dataList);
("移除 List<String> 中的 null 和空字符串:");
(filteredListStrings); // 输出: [apple, banana]
}
}

注意: `().isEmpty()` 比 `()` 更健壮,它会先移除字符串两端的空白字符,再判断是否为空。

五、性能考量与最佳实践
选择合适的方法:

对于小型数据集或对性能要求不高的场景,传统迭代和 Stream API 的性能差异可以忽略不计。
对于大型数据集或需要高并发处理的场景,Stream API 的并行流 (`.parallelStream()`) 可能提供更好的性能,但需要注意并行流的开销和线程安全问题。
Stream API 提供了更简洁、可读性更高的代码,通常是现代 Java 开发的首选。


空输入处理: 始终检查输入数组或集合本身是否为 `null`,以避免 `NullPointerException`。在示例中,我们通常会在开头进行 `if (sourceArray == null)` 判断并返回空集合。
结果类型:

如果结果需要是 `List`,使用 `collect(())`。
如果结果需要是数组,使用 `toArray(T[]::new)`。注意,对于泛型数组,直接创建 `new T[0]` 是不允许的,需要传入数组生成器。


不可变性: 过滤操作通常会创建一个新的集合或数组,而不是修改原始数据。这符合函数式编程的原则,有助于避免副作用,提高代码的健壮性。
泛型化: 尽可能编写泛型方法,以便于复用,处理不同类型的数组或集合。

六、总结

在 Java 中处理和移除空数组、`null` 引用以及空集合是数据清洗和预处理的重要一环。从传统的迭代方法到现代的 Stream API,Java 提供了多种工具来实现这一目标。Stream API 以其声明式、函数式的风格,为开发者带来了更简洁、更富有表达力的代码,是处理此类问题的推荐方法。

无论是处理二维数组中的空数组,还是过滤嵌套集合中的空子集合,掌握 `filter()` 结合 `Objects::nonNull` 和 `length > 0` (或 `!isEmpty()`) 的技巧,都能让你的代码更加健壮和高效。通过恰当地应用这些策略,你可以有效地提升代码质量,减少潜在的运行时错误,并专注于核心业务逻辑的实现。

2026-03-10


上一篇:Spark Java开发实战:核心API与常用方法深度解析

下一篇:Java数组数据逆转:从原理到实践的深度指南