函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算。

为什么Java需要Lambda表达式进行函数式编程呢?在我看来,有以下好处:

  • 1、面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象;
  • 2、增加Lambda表达式,让代码在多核服务器上更高效运行;
  • 3、一直以来,Java一直被诟病的地方,就是代码写得比较啰嗦,简单的CURD都要写好几行代码,Java8推出函数式编程后,啰嗦且冗长的代码得到极大改观,用Java也能写出简洁优美的代码。

不多啰嗦,下面开始函数式编程之Stream流处理的方法和案例讲解。

1. 引言

Streams API已在Java 8中引入,并且已经是Java语言规范的一部分多年了。尽管如此,平时的工作中,还是有很多项目还停留在java1.7中的使用中,而且java8的很多新特性都是革命性的,尤其是stream流处理。因此,这篇文章里,将介绍Streams的基本概念,并通过一些示例对其进行解释。

本文参考自官方Java文档java-stream流处理。与集合相比,Streams的一些特征(取自官方文档)如下:

  • 流不存储数据。相反,它们是数据的来源。
  • 流本质上是功能性的,这可能就是为什么流对大多数人来说很难理解的原因。它们产生一个结果,例如,像一个总和或一个新的流。
  • 流的许多操作都是惰性求值的(laziness-seeking)。操作可以是中间操作,也可以是终止操作。我们将在下一节中介绍这两者。惰性求值是指该操作只是对stream的一个描述,并不会马上执行。这类惰性的操作在stream中被称为中间操作(intermediate operations)。例如,查找流中元素的第一个匹配项。不必检查流的所有元素。找到第一个匹配项后,可以结束搜索。
  • 可能是无限的。只要流生成数据,流就不会结束。
  • 流的元素只能访问一次,和迭代器 Iterator 相似,当需要重复访问某个元素时,需要重新生成一个新的stream。

在接下来的部分中,我们将介绍如何创建 Streams,介绍一些中间操作,最后我们将介绍一些终止操作。这些源代码可以在结尾的github源码里提供,不需要自己复制粘贴。

2. 创建流源

有几种方法可以创建流。它们可以从集合,数组,文件等创建。在本节中,我们将创建一些测试,向您展示如何以不同的方式创建流。我们将创建并使用终止操作,以便将 消费到 .我们不对 执行任何其他操作,我们将它留给其他部分。最后,我们断言是否与我们期望的相同。测试位于单元测试中。

2.1 从集合创建流

使用 java.util.Collection.stream() 方法

private static final List<String> stringsList = Arrays.asList("a", "b", "c");@Test
public void createStreamsFromCollection() {List<String> streamedStrings = stringsList.stream().collect(Collectors.toList());assertLinesMatch(stringsList, streamedStrings);
}

2.2 从数组创建流

使用 java.util.Arrays.stream(T[]array)方法

@Test
public void createStreamsFromArrays() {List<String> streamedStrings = Arrays.stream(new String[]{"a", "b", "c"}).collect(Collectors.toList());assertLinesMatch(stringsList, streamedStrings);
}

2.3 从流创建流。

使用 Stream的静态方法:of()、iterate()、generate()

@Test
public void createStreamsFromStreamOf() {List<String> streamedStrings = Stream.of("a", "b", "c").collect(Collectors.toList());assertLinesMatch(stringsList, streamedStrings);
}

2.4 从 IntStream 创建流

使用将int值作为参数来创建。

@Test
public void createStreamsFromIntStream() {int[] streamedInts = IntStream.of(1, 2, 3).toArray();assertArrayEquals(new int[]{1, 2, 3}, streamedInts);
}

2.5 从文件创建流

从文件中读取进行创建。

    @Testpublic void createStreamsFromFile() {try {List<String> expectedLines = Arrays.asList("file1", "file2", "file3","file4");BufferedReader reader = new BufferedReader(new FileReader(new File(System.getProperty("user.dir")+"/src/main/resources/file.txt")));List<String> streamedLines = reader.lines().collect(Collectors.toList());assertLinesMatch(expectedLines, streamedLines);} catch (FileNotFoundException e) {e.printStackTrace();}}

3. 中间操作(intermediate operations)

中间操作(intermediate operations)指的是将一个stream转换为另一个stream的操作,譬如filter和map操作。这些操作返回新的,但不返回最终结果。中间操作可以分为无状态操作(不保留有关先前处理的元素的信息)和有状态操作(可能需要在生成中间结果之前处理所有元素)。

可以在 Streams API 中找到可调用的操作的完整列表。

public class Shoe {private int id;private String brand;private String size;private String color;public Shoe(int id, String brand, String size, String color) {this.id = id;this.brand = brand;this.size = size;this.color = color;}...}

在单元测试中,我们定义了四个对象,我们将在下一个示例中使用:shoe

    private static final Shoe volkswagenGolf = new Shoe(0, "Volkswagen", "L", "blue");private static final Shoe skodaOctavia = new Shoe(1, "Skoda", "XL", "green");private static final Shoe renaultKadjar = new Shoe(2, "Renault", "XXL", "red");private static final Shoe volkswagenTiguan = new Shoe(3, "Volkswagen", "M", "red");

3.1 无状态操作

3.1.1 filter操作

该操作允许我们基于给定的.在示例中,我们首先创建4双鞋子中的一双,然后创建一双仅包含Volkswagen鞋子的新鞋子。

@Test
public void filterStream() {List<Shoe> expectedShoes = Arrays.asList(volkswagenGolf, volkswagenTiguan);List<Shoe> filteredShoes = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).filter(shoe -> shoe.getBrand().equals("Volkswagen")).collect(Collectors.toList());assertIterableEquals(expectedShoes, filteredShoes);
}

3.1.2 map操作

在前面的所有示例中,源中的类型和结果始终相同。通常,您希望对每个元素应用一个函数。例如,如果我们希望结果仅包含品牌而不是对象,我们可以使用该操作并将该方法应用于每个元素,从而产生仅具有品牌名称的新元素。

@Test
public void mapStream() {List<String> expectedBrands = Arrays.asList("Volkswagen", "Skoda", "Renault", "Volkswagen");List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getBrand).collect(Collectors.toList());assertIterableEquals(expectedBrands, brands);
}

3.1.3 组合过滤和映射操作 (filter+map)

当然,将操作组合并将它们链接在管道中是完全有效的。在下一个示例中,我们进行筛选以检索Volkswagen鞋子,然后使用该操作仅检索颜色。这样,我们最终会得到一个包含Volkswagen鞋子颜色的列表。

@Test
public void filterMapStream() {List<String> expectedColors = Arrays.asList("blue", "red");List<String> volkswagenColors = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).filter(shoe -> shoe.getBrand().equals("Volkswagen")).map(Shoe::getColor).collect(Collectors.toList());assertIterableEquals(expectedColors, volkswagenColors);
}

3.2 有状态操作

3.2.1 distinct操作

不同操作将返回仅包含基于元素的方法实现的不同元素的。在下一个示例中,我们首先检索一个品牌,然后对其执行操作。这就产生了我们使用的三个不同品牌的列表。

@Test
public void distinctStream() {List<String> expectedBrands = Arrays.asList("Volkswagen", "Skoda", "Renault");List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getBrand).distinct().collect(Collectors.toList());assertIterableEquals(expectedBrands, brands);
}

3.2.2 sorted操作

该操作将根据其自然顺序对元素进行排序。也可以使用 a 作为参数,以便进行更自定义的排序。下一个示例将从 中检索品牌并按字母顺序对其进行排序。

@Test
public void sortedStream() {List<String> expectedSortedBrands = Arrays.asList("Renault", "Skoda", "Volkswagen", "Volkswagen");List<String> brands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getBrand).sorted().collect(Collectors.toList());assertIterableEquals(expectedSortedBrands, brands);
}

3.3 用于调试目的的peek

我们将讨论的最后一个中间操作是操作。这是一个特殊的,主要用于调试目的。 将在使用元素时对其执行操作。让我们以组合的过滤器和地图为例,并向其添加一些操作,以便打印正在使用的元素。

@Test
public void peekStream() {List<String> expectedColors = Arrays.asList("blue", "red");List<String> volkswagenColors = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).filter(shoe -> shoe.getBrand().equals("Volkswagen")).peek(e -> System.out.println("Filtered value: " + e)).map(Shoe::getColor).peek(e -> System.out.println("Mapped value: " + e)).collect(Collectors.toList());assertIterableEquals(expectedColors, volkswagenColors);
}

此测试的输出为:

Filtered value: Shoe{id=0, brand='Volkswagen', type='Golf', color='blue'}Mapped value: blueFiltered value: Shoe{id=3, brand='Volkswagen', type='Tiguan', color='red'}Mapped value: red

4. 终止操作(terminal operations)

终止操作(terminal operations)则指的是那些会产生一个新值或副作用(side-effect)的操作,例如count 和 forEach 操作。

4.1 collecti操作

我们已经在前面的所有示例中使用了终止操作。我们总是在操作中使用参数。不过还有更多选项可以使用。我们将在下一个示例中介绍一些内容。完整的列表可以在JavaDoc中找到。

方法collect里使用 ,我们可以使用方法Collectors.joining进行分隔符分隔。

@Test
public void collectJoinStream() {String expectedBrands = "Volkswagen;Skoda;Renault;Volkswagen";String joinedBrands = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getBrand).collect(Collectors.joining(";"));assertEquals(expectedBrands, joinedBrands);
}

Collectors.summingInt使用 ,我们可以计算元素的属性之和。在我们的示例中,我们只计算鞋子 id 的总和。

@Test
public void collectSummingIntStream() {int sumIds = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).collect(Collectors.summingInt(Shoe::getId));assertEquals(6, sumIds);
}

Collectors.groupingBy使用 ,我们可以将元素分组到 .在我们的示例中,我们根据品牌对元素进行分组。

@Test
public void collectGroupingByStream() {Map<String, List<Shoe>> expectedShoes = new HashMap<>();expectedShoes.put("Skoda", Arrays.asList(skodaOctavia));expectedShoes.put("Renault", Arrays.asList(renaultKadjar));expectedShoes.put("Volkswagen", Arrays.asList(volkswagenGolf, volkswagenTiguan));Map<String, List<Shoe>> groupedShoes = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).collect(Collectors.groupingBy(Shoe::getBrand));assertTrue(expectedShoes.equals(groupedShoes));
}

4.2 reduce操作

reduce,该操作对 的元素执行减量。它使用恒等式(即起始值)和执行归约的累积函数。在我们的示例中,我们对元素的 id 执行求和,就像我们使用该操作时所做的那样。

@Test
public void reduceStream() {int sumIds = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getId).reduce(0, Integer::sum);assertEquals(6, sumIds);
}

4.3 forEach 操作

该操作对每个元素执行一个函数。它与中间操作非常相似。

@Test
public void forEachStream() {Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).forEach(System.out::println);
}

该测试的结果如下:

Shoe{id=0, brand='Volkswagen', type='Golf', color='blue'}Shoe{id=1, brand='Skoda', type='Octavia', color='green'}Shoe{id=2, brand='Renault', type='Kadjar', color='red'}Shoe{id=3, brand='Volkswagen', type='Tiguan', color='red'}

4.4 count操作

该操作对流中的元素数进行计数。

@Test
public void countStream() {long count = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).count();assertEquals(4, count);
}

4.5 max操作

该操作返回基于给定的 的最大元素。在我们的示例中,我们创建一个 id 并检索具有最高 id 的元素。请注意,这将返回一个 .当无法确定最高 id 时,我们只是提供了一个替代值。

@Test
public void maxStream() {int maxId = Stream.of(volkswagenGolf, skodaOctavia, renaultKadjar, volkswagenTiguan).map(Shoe::getId).max((o1, o2) -> o1.compareTo(o2)).orElse(-1);assertEquals(3, maxId);
}

5. 结论

Java Streams API非常强大,学习起来并不难。stream流处理可读性还是很不错的,想让自己代码变得更加简洁美观,那就来跟着本文学习吧,还有源码跟着调试。
如果您还不熟悉 Streams API,可以安装IDEA的插件工具,调试 Java 流的出色 IntelliJ 功能。

本文案例的源码地址:beierzsj/javaStreamDemo (github.com)

参考文档

  • java-stream流处理
  • 集合
  • Streams API
  • JavaDoc
  • 调试 Java 流的IDEA工具安装插件

博主个人博客网站:奇想派

本文原创作者:奇想派、一名努力分享的程序员。

文章首发平台:微信公众号【编程达人】

java8函数式编程之Stream流处理的方法和案例讲解相关推荐

  1. 【Java八股文之基础篇(十九)】函数式编程之Stream流(上)

    Stream流 概述 Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作.可以更方便的让我们对集合或数组操作. 案例数据准备 <depe ...

  2. Java8函数式编程、Stream流、Option的使用,一篇足够了

    为什么要学 大数量下处理集合效率高 代码可读性高 消灭嵌套地狱 // 查询未成年作家的评分在70以上的书籍 List<Book> bookList = new ArrayList<& ...

  3. Java23-day14【函数式接口(Supplier\Consumer\Predicate\Function)、Stream流(生产方式\中间方法\终结方法)】

    视频+资料[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg   提取码:zjxs] Java基础--学习笔记(零起点打开java世界的大门)--博 ...

  4. 使用Java8新特性(stream流、Lambda表达式)实现多个List 的笛卡尔乘积 返回需要的List<JavaBean>

    需求分析: 有两个Long类型的集合 : List<Long> tagsIds; List<Long> attributesIds; 现在需要将这两个Long类型的集合进行组合 ...

  5. JUC源码级学习(下)-函数式编程、stream流、jvm等

    12.四大函数式接口(必需掌握) 新时代的程序员:lambda表达式.链式编程.函数式接口.Stream流式计算 函数式接口: 只有一个方法的接口 @FunctionalInterface publi ...

  6. 常用函数式接口,Stream流

    01.第一章:常用函数式接口_Predicate判断接口: 1).java.util.Function.Prdedicate(函数式接口): 2).抽象方法:1).boolean test(T t) ...

  7. Stream流—常用简单方法

    说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个 ...

  8. android include 点击事件,Android编程之include文件的使用方法

    本文实例分析了Android编程之include文件的使用方法.分享给大家供大家参考,具体如下: 记得很久以前,听一位大神说,程序员都很懒,不懒惰的程序员不是好程序员,当时不明白什么意思.后来慢慢的懂 ...

  9. Java中 方法引用、Stream流、及方法实例 D190401

    Java中 方法引用.Stream流.及方法实例 D190401 01.第三章:方法引用_什么是方法引用 1).什么是"方法引用":指引用现有的方法代替Lambda表达式--当我们 ...

最新文章

  1. 面试官:一个线程OOM,进程里其他线程还能运行么?
  2. Java深入了解String对象
  3. 27. 代码实例-spring声明式事务
  4. Linux下的tar归档及解压缩功能详解
  5. Scrum立会报告+燃尽图(十一月十八日总第二十六次):功能开发与讨论贡献分配规则...
  6. A query was run and no Result Maps were found for the Mapped Statement....
  7. C++ 多态实现的三个条件
  8. 光立方原理讲解_90%人不理解什么是防眩光射灯 防昡晕 防炫光,。怎么选项led防眩灯...
  9. hadoop中的helloword
  10. windows平台源码编译最新版openssl
  11. 善用佳软:如何使用Beyond Compare比对class文件
  12. css3实现椭圆轨迹运动
  13. 怎样让机器有人类思维
  14. 前端Vue、后端SSM、前后端分离项目服务器部署实战
  15. MySql(三)——事务和锁
  16. 秘密是如何被泄露的?自建文件分享神器HFS
  17. 提高写作能力与表达能力
  18. MassGrid分布式计算网络
  19. 计算机网络(第五版)第五章——习题解答
  20. 如何动态设置Picture图形控件的位图

热门文章

  1. ART-PI平台移植Touchgfx 驱动gt9147 触摸屏幕点亮LED
  2. 【依存树】短语结构树转化依存树
  3. c++下Gdal将16bit的tif图像转8bit
  4. SX1276/77/78学习笔记2 - sx1278工作方式
  5. python垃圾回收机制原理_详解python的垃圾回收机制
  6. centos7安装hadoop集群
  7. 并发编程 | Netty - [常用组件概要]
  8. 迭代法求平方根C语言版(1021)
  9. 计算机绘图水平测试,全国计算机应用水平考试 计算机绘图考试大纲(2019 年版).pdf...
  10. 1、OLED简单驱动