Java 8 中引入的函数式风格是对该语言的一个重要补充。现在Java不再是纯粹的OOP,而是OOP和函数式编程的混合体。这是一个游戏规则的改变者,我们需要改变我们的 OOP 大脑来吸收这些变化。 但我们为什么要接受这些改变呢?当我们可以使用纯 OOP 解决问题时,为什么还要浪费时间去尝试函数式风格呢?
-
Java 8中引入的函数式风格帮助我们缩小了业务逻辑和代码之间的差距。它使我们能够在更高层次上以自然流畅的方式讲述故事。你可以说你想做什么,而不是说你想怎么做。
-
代码变得更干净、更简洁。
-
高阶函数使我们能够:
- 将函数发送给其他函数
- 在其他函数中创建函数
- 从其他函数返回函数
这对于 Java 来说是一个巨大的胜利,我们需要发送、创建和返回对象来执行此操作。我们将能够编写更可靠、更有针对性、更易于重用的代码。
-
感谢 lambda,我们可以进行惰性计算。当 lambda 表达式作为方法参数发送时,编译器将在方法中调用它时对其进行计算。这与立即评估的普通方法参数不同。
-
Lambda 使编写单元测试变得有趣。它们使我们能够创建干净、体积小且编写速度快的轻量级测试。我们可以使用 lambda 来根除被测代码。这使我们能够测试各种场景将如何影响代码。
-
学习新模式。
-
以及更多!
break
,极大地改变了循环的行为,迫使我们不仅要理解代码试图实现的目标,continue
还要return
理解循环是如何工作的。现在我们将看看如何将循环转换为更简洁和可读的代码。
让编码开始吧!
我们将使用文章。一篇文章有标题、作者和几个标签。private class Article {
private final String title;
private final String author;
private final List<String> tags;
private Article(String title, String author, List<String> tags) {
this.title = title;
this.author = author;
this.tags = tags;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public List<String> getTags() {
return tags;
}
}
每个示例都将包含使用循环的传统解决方案和使用 Java 8 新功能的解决方案。在第一个示例中,我们希望找到集合中带有标签“Java”的第一篇文章。让我们看一下使用循环的解决方案。
public Article getFirstJavaArticle() {
for (Article article : articles) {
if (article.getTags().contains("Java")) {
return article;
}
}
return null;
}
现在让我们使用 Stream API 中的操作来解决该问题。
public Optional<Article> getFirstJavaArticle() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.findFirst();
}
很酷,不是吗?我们首先使用该操作filter
查找所有带有“Java”标签的文章,然后使用该操作findFirst()
来获取第一个出现的文章。由于流是惰性的并且过滤器返回流,因此这种方法只会处理元素,直到找到第一个匹配项。现在让我们获取所有标记为“Java”的文章,而不仅仅是第一篇。首先是使用循环的解决方案。
public List<Article> getAllJavaArticles() {
List<Article> result = new ArrayList<>();
for (Article article : articles) {
if (article.getTags().contains("Java")) {
result.add(article);
}
}
return result;
}
使用流操作的解决方案。
public List<Article> getAllJavaArticles() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.collect(Collectors.toList());
}
在此示例中,我们使用操作collect
来缩短结果流,而不是声明集合并显式添加匹配的条目。到目前为止,一切都很好。是时候让 Stream API 真正发挥作用的示例了。让我们按作者对所有文章进行分组。像往常一样,我们首先使用循环来解决它:
public Map<String, List<Article>> groupByAuthor() {
Map<String, List<Article>> result = new HashMap<>();
for (Article article : articles) {
if (result.containsKey(article.getAuthor())) {
result.get(article.getAuthor()).add(article);
} else {
ArrayList<Article> articles = new ArrayList<>();
articles.add(article);
result.put(article.getAuthor(), articles);
}
}
return result;
}
我们可以使用流操作找到一个干净的解决方案来解决这个问题吗?
public Map<String, List<Article>> groupByAuthor() {
return articles.stream()
.collect(Collectors.groupingBy(Article::getAuthor));
}
惊人的!通过使用操作groupingBy
和方法引用getAuthor()
,我们可以获得干净且可读的代码。现在让我们找到集合中使用的其余标签。让我们从一个循环示例开始:
public Set<String> getDistinctTags() {
Set<String> result = new HashSet<>();
for (Article article : articles) {
result.addAll(article.getTags());
}
return result;
}
好吧,让我们看看如何使用流操作来解决这个问题:
public Set<String> getDistinctTags() {
return articles.stream()
.flatMap(article -> article.getTags().stream())
.collect(Collectors.toSet());
}
凉爽的!flatmap
帮助我们将标签列表扁平化为单个结果流,然后我们用它collect
来创建返回集。
GO TO FULL VERSION