
任务概述
在编程实践中,我们经常需要处理文本数据。其中一个常见任务是从一个包含多个单词的句子中,根据指定的单词长度,筛选并返回所有符合条件的单词。例如,给定字符串“monday is a new day”和长度3,我们期望得到{"new", "day"}。
传统的实现方式可能涉及手动遍历字符串,判断字符是否为空格来识别单词边界,然后截取子字符串并检查其长度。这种方法通常代码量较大,逻辑复杂,且容易出错,尤其是在处理多个连续空格或字符串开头/结尾的空格时。
推荐解决方案:结合split()与Stream API
Java 8引入的Stream API为处理集合数据提供了强大而简洁的工具。结合String.split()方法,我们可以非常优雅地解决这个问题。
核心思路
- 分解字符串: 使用String.split()方法将输入的句子按空格分割成一个单词数组。
- 创建流: 将单词数组转换为一个流(Stream)。
- 过滤单词: 使用流的filter()操作,根据每个单词的长度是否等于目标长度进行筛选。
- 收集结果: 使用toArray()操作将过滤后的单词收集到一个新的字符串数组中。
示例代码
以下是实现上述逻辑的Java代码:
import java.util.Arrays;
import java.util.Objects; // 用于Objects.requireNonNullElseGet,处理null或空字符串
public class WordExtractor {
/**
* 从给定字符串中提取所有指定长度的单词。
*
* @param sentence 输入的句子字符串。
* @param wordLength 目标单词的长度。
* @return 包含所有符合长度要求的单词的字符串数组。
* 如果输入句子为空或null,则返回空数组。
*/
public String[] findWordsByLength(String sentence, int wordLength) {
// 1. 处理null或空字符串输入,避免NullPointerException
// Objects.requireNonNullElseGet(sentence, () -> "") ensures sentence is not null
// .trim() removes leading/trailing spaces
// .split("\\s+") splits by one or more whitespace characters
String[] words = Objects.requireNonNullElseGet(sentence, () -> "")
.trim()
.split("\\s+");
// 2. 将单词数组转换为流,并进行过滤和收集
return Arrays.stream(words)
.filter(word -> !word.isEmpty() && word.length() == wordLength) // 过滤空字符串和符合长度的单词
.toArray(String[]::new); // 将结果收集到新的字符串数组中
}
public static void main(String[] args) {
WordExtractor extractor = new WordExtractor();
// 示例1
String s1 = "Monday is a new day";
int n1 = 3; // 3字母单词
String[] result1 = extractor.findWordsByLength(s1, n1);
System.out.println("Input: \"" + s1 + "\", Length: " + n1 + " -> Result: " + Arrays.toString(result1)); // 预期: {"new", "day"}
// 示例2
String s2 = "Monday is a new day";
int n2 = 2; // 2字母单词
String[] result2 = extractor.findWordsByLength(s2, n2);
System.out.println("Input: \"" + s2 + "\", Length: " + n2 + " -> Result: " + Arrays.toString(result2)); // 预期: {"is"}
// 示例3:包含多个空格
String s3 = " hello world java ";
int n3 = 5;
String[] result3 = extractor.findWordsByLength(s3, n3);
System.out.println("Input: \"" + s3 + "\", Length: " + n3 + " -> Result: " + Arrays.toString(result3)); // 预期: {"hello", "world"}
// 示例4:空字符串或null输入
String s4 = "";
int n4 = 3;
String[] result4 = extractor.findWordsByLength(s4, n4);
System.out.println("Input: \"" + s4 + "\", Length: " + n4 + " -> Result: " + Arrays.toString(result4)); // 预期: {}
String s5 = null;
int n5 = 3;
String[] result5 = extractor.findWordsByLength(s5, n5);
System.out.println("Input: \"" + s5 + "\", Length: " + n5 + " -> Result: " + Arrays.toString(result5)); // 预期: {}
}
}代码解析
- Objects.requireNonNullElseGet(sentence, () -> ""): 这是一个健壮性处理,确保输入的sentence参数即使为null,也不会导致NullPointerException。如果sentence是null,它会替换为一个空字符串""。
- .trim(): 调用trim()方法去除字符串开头和结尾的空白字符。这有助于确保split()方法不会产生空字符串作为单词(例如," hello"经过split(" ")可能得到{"", "hello"})。
-
.split("\\s+"): 这是关键一步。
- split()方法根据给定的正则表达式将字符串分割成子字符串数组。
- "\\s+"是一个正则表达式,表示匹配一个或多个空白字符(包括空格、制表符、换行符等)。使用"\\s+"比简单的" "更健壮,可以正确处理句子中包含多个连续空格的情况(例如"hello world")。
- 此操作将返回一个String[],其中每个元素都是一个单词。
-
Arrays.stream(words): 将上一步得到的words数组转换为一个Stream
。Stream API的所有操作都基于此流进行。 -
.filter(word -> !word.isEmpty() && word.length() == wordLength): 这是流的中间操作,用于过滤元素。
- word -> !word.isEmpty():过滤掉可能由split()操作产生的空字符串。尽管trim()和split("\\s+")组合通常能避免这种情况,但多一层防御总是有益的。
- word.length() == wordLength:这是核心过滤条件,只保留长度与wordLength相等的单词。
- .toArray(String[]::new): 这是流的终止操作,将过滤后的流中的所有元素收集到一个新的String数组中。String[]::new是构造函数引用,用于指定创建数组的类型。
最佳实践与注意事项
- 描述性命名: 在代码中,使用具有描述性的方法名(如findWordsByLength而非howManyWord)和参数名(如wordLength而非n)至关重要。这大大提高了代码的可读性和可维护性,让其他开发者(或未来的你)能一眼理解代码的功能。
-
正则表达式的选用:
- split(" "):只按单个空格分割。如果字符串中有多个连续空格(如"hello world"),split(" ")会产生空字符串{"hello", "", "world"}。
- split("\\s+"):按一个或多个空白字符分割。这是更推荐的做法,因为它能更鲁棒地处理各种空白字符(空格、制表符、换行符)以及连续的空白字符,避免产生空字符串。
- 处理空字符串或null输入: 在实际应用中,输入字符串可能为空或null。在调用split()之前,进行null检查和空字符串处理是良好的编程习惯,以避免运行时错误。Objects.requireNonNullElseGet是一个优雅的解决方案。
- 性能考量: 对于大多数常见的字符串长度和单词数量,Stream API的性能非常优秀。它在内部进行了优化,并且代码表达力强。对于极端性能敏感的场景(例如处理GB级别文本且需要微秒级响应),可能需要考虑更底层的字符遍历优化,但这种情况相对较少。
- 标点符号处理: 如果单词中可能包含标点符号(如"day."),而你希望只匹配纯字母单词,你可能需要在split()之前或filter()之后额外添加一步处理,例如使用word.replaceAll("[^a-zA-Z]", "")来去除标点符号。本教程的示例假定单词不含标点。
总结
通过结合String.split()方法和Java 8 Stream API,我们可以用非常简洁、高效且易于理解的方式,从字符串中提取指定长度的单词。这种现代Java编程风格不仅提升了代码质量,也降低了维护成本。掌握这种模式对于处理文本数据和利用Java函数式编程特性至关重要。
立即学习“Java免费学习笔记(深入)”;










