Java8 中Stream API介绍

  Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

  流(Stream)的概念:流是数据渠道,用于操作数据(集合、数组等)所生成的元素序列。

注意:

  1. Stream自己不会存储元素。
  2. Stream不会改便源对象,相反,它们会返回一个持有结果的新Stream。
  3. Stream操作是延迟执行的,这意味着他们会等到需要结果的时候执行。

Stream的操作三个步骤:

  • 创建Stream:一个数据源(如数组、集合),获取一个流
  • 中间操作:一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果

创建Stream

创建流的方式有如下几种方式:

    //创建Stream
@Test
public void test01(){
//1.可以通过Collection系列集合提供的stream() 或 parallelStream()
List<String> list = new ArrayList();
Stream<String> stream01 = list.stream(); //2、通过Arrays中的静态方法stream() 获取数组流
Emp[] emps = new Emp[10];
Stream<Emp> stream02 = Arrays.stream(emps); //3.通过Stream类中的静态方法of()
Stream<String> stream03 = Stream.of("aa","bb","cc"); //4.创建无限流
//迭代
Stream<Integer> stream04 = Stream.iterate(0,(x) -> x+2);
stream04.forEach(System.out::println);
//只要10个数据
stream04.limit(10).forEach(System.out::println); //生成5个随机数
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
}

Stream中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。

筛选与切片:

  • filter -------- 接受Lambda ,从流中排除某些元素
  • limit --------- 截断流,使其元素不超过给定数量
  • skip(n) ------- 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
  • distinct ------ 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素

映射:

  • map ------ 接收Lambda,将元素转换为其他形式或提取信息(接受一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素)
  • flatMap ---- 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有的流连凑成一个流。

排序:

  • sorted() ---- 自然排序
  • sorted(Comparator comparator) ------ 定制排序(Comparator )

下面通过代码来练习这些中间操作,先创建一个Employee实体类:

public class Employee {

    private String name;

    private Integer age;

    private Double salary;

    public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public Double getSalary() {
return salary;
} public void setSalary(Double salary) {
this.salary = salary;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(age, employee.age) &&
Objects.equals(salary, employee.salary);
} @Override
public int hashCode() {
return Objects.hash(name, age, salary);
} @Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}

测试中间操作filter的用法:

    List<Employee> emps = Arrays.asList(
new Employee("张三",21,4500.00),
new Employee("李四",25,6000.00),
new Employee("王五",56,3500.00),
new Employee("王五",56,3500.00),
new Employee("田七",30,8000.00),
new Employee("田七",30,8000.00)
); @Test
public void test02(){
//中间操作:不会执行任何操作
Stream<Employee> stream = employeeList.stream().filter(e -> e.getAge()>25);
//终止操作:一次型执行全部内容,即”惰性求值“
//内部迭代:迭代操作由Stream API 完成
stream.forEach(System.out::println);
} //外部迭代
@Test
public void test03(){
Iterator<Employee> it = emps.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}

中间操作:limit --只要找到符合条件的指定条数数据,就不会执行后面的数据过滤操作了,可以提高效率

    @Test
public void test04(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.limit(2) //短路 : 只要找到符合条件的两条数据,就不会执行后面的数据过滤操作了,可以提高效率
.forEach(System.out::println);
}

中间操作:skip(n) ----跳过n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

    @Test
public void test05(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.skip(2)
.forEach(System.out::println);
}

中间操作: distinct ---- 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素

    @Test
public void test06(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.distinct ()
.forEach(System.out::println);
}

中间操作: map ------ 接受一个函数作为参数,该函数或被应用到每个元素上,并将其映射成一个新的元素

    @Test
public void test07(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
list.stream()
//toUpperCase()函数被应用到流中每个元素上,并将其映射成一个新的元素
.map((str) -> str.toUpperCase())
.forEach(System.out::println); //输出结果:AAA BBB CCC DDD HELLO
System.out.println("------------------------------"); emps.stream()
.map(Employee::getName)
.forEach(System.out::println);//输出结果:张三 李四 王五 王五 田七 田七
System.out.println("------------------------------"); Stream<Stream<Character>> stream01 = list.stream()
//调用filterCharacter(),将流中的字符串元素都转为字符流,返回值类型为Stream<Stream<Character>>
.map(StreamApiTest::filterCharacter);
stream01.forEach((sm) -> {
sm.forEach(System.out::println);
});
System.out.println("------------------------------");
} //字符串转为字符流
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}

中间操作: flatMap --- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连凑成一个流

    @Test
public void test08(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
Stream<Character> stream02 = list.stream()
//调用filterCharacter(),将流中的字符串元素都转为字符流,并将这些流加入到一个新流中,返回值类型为Stream<Character>
.flatMap(StreamApiTest::filterCharacter);
stream02.forEach(System.out::println);
}

中间操作: sorted() ---- 自然排序

    @Test
public void test09(){
List<String> list = Arrays.asList("ccc","aaa","ddd","bbb","eee");
list.stream().sorted().forEach(System.out::println);
}

中间操作: sorted(Comparator comparator) ------ 定制排序(Comparator )

自定义排序规则,这个根据员工年龄排序,若员工年龄相同,则根据员工姓名排序 --- 升序

    @Test
public void test10(){
emps.stream()
.sorted((e1,e2) -> {
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}

终止操作

查找与匹配:

  • allMatch ----- 检查是否匹配所有元素
  • anyMatch ------ 检查是否至少匹配一个元素
  • noneMatch --------- 检查是否没有匹配所有元素
  • findFirst ------- 返回第一个元素
  • findAny -------- 返回流中的任意元素
  • count ----------- 返回流中元素的总个数
  • max ------- 返回流中的最大值
  • min ------ 返回流中的最小值

归约:可以将流中元素反复结合起来,得到一个值

  • reduce(T indentity,BinaryOperator bin) ---- indentity 为起始值
  • reduce(BinaryOperator bin)

收集:

  • collect ----- 将流装换为其它形式,接受一个Collector接口的实现,用于给Stream中元素汇总的方法

终止操作练习:在此之前,我们先创建一个员工实体类,方便测试效果

public class Employee {

    private String name;

    private Integer age;

    private Double salary;

    private Status status;

    public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
} public Employee(String name, Integer age, Double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
} public Status getStatus() {
return status;
} public void setStatus(Status status) {
this.status = status;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public Double getSalary() {
return salary;
} public void setSalary(Double salary) {
this.salary = salary;
} @Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return Objects.equals(getName(), employee.getName()) &&
Objects.equals(getAge(), employee.getAge()) &&
Objects.equals(getSalary(), employee.getSalary()) &&
getStatus() == employee.getStatus();
} @Override
public int hashCode() { return Objects.hash(getName(), getAge(), getSalary(), getStatus());
} public enum Status{
BUSY,FREE,VACATION;
}
}

接下来我们先对 查找与匹配 中的几个终止操作进行代码测试:

    List<Employee> employees = Arrays.asList(
new Employee("张三",21,4500.00, Employee.Status.BUSY),
new Employee("李四",25,6000.00,Employee.Status.VACTION),
new Employee("王五",56,3500.00,Employee.Status.BUSY),
new Employee("王五",56,3500.00,Employee.Status.BUSY),
new Employee("田七",30,8000.00,Employee.Status.FREE),
new Employee("田七",30,8000.00,Employee.Status.FREE)
); @Test
public void test11(){
//allMatch ----- 检查是否匹配所有元素
boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(b1); //输出结果: false //anyMatch ------ 检查是否至少匹配一个元素
boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2); //输出结果: true //noneMatch --------- 检查是否没有匹配所有元素
boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3); //输出结果: false //findFirst ------- 返回第一个元素
//Optional --- 是Java8 提供的处理空指针异常的类
Optional<Employee> employee = employees.stream()
//按员工的薪资排序
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
//获取第一个员工信息,即薪资最新的员工信息
.findFirst();
System.out.println(employee.get()); //findAny -------- 返回流中的任意元素
//parallelStream --- 获取并行流
Optional<Employee> any = employees.parallelStream()
//获取状态为 FREE 的任意一个员工信息
.filter(e -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(any.get());
}

测试终止操作中,count,max,min的运用

    @Test
public void test12(){
//返回流中元素的总个数
long count = employees.stream().count();
System.out.println(count); //输出结果为:6 Optional<Employee> max = employees.stream().
//获取年龄最大的员工信息
max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(max.get()); // 输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY} Optional<Double> min = employees.stream()
.map((e) -> e.getSalary())
//获取最低的薪资
.min(Double::compare);
System.out.println(min.get());//输出结果为:3500.0
}

终止操作:归约 ---- reduce(T indentity,BinaryOperator) / reduce(BinaryOperator),可以将流中元素反复结合起来,得到一个值

    @Test
public void test13(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list.stream()
//0为初始值,将流中的元素按照 lambda体中的方式进行汇总,这里即是通过求和的方式汇总
.reduce(0, (x, y) -> x + y);
System.out.println(reduce); // 输出结果为:55 Optional<Double> sum = employees.stream()
//获取员工的薪资信息
.map((e) -> e.getSalary())
//调用Double的sum(),对员工薪资进行求和
.reduce(Double::sum);
System.out.println(sum.get()); //输出结果为:33500.0
}

终止操作:收集:collect ------- 将流装换为其它形式,接收一个Collector 接口的实现,用于给Stream中元素汇总的方法

    @Test
public void test14(){ //Collectors工具类对Collector接口提供了很多实现
List<String> list = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
System.out.println(list);//输出结果为:[张三, 李四, 王五, 王五, 田七, 田七] Set<Integer> set = employees.stream()
.map(e -> e.getAge())
.collect(Collectors.toSet());
System.out.println(set);//输出结果为:[21, 56, 25, 30] HashSet<String> hashSet = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);//输出结果为:[李四, 张三, 王五, 田七] }

因为collect收集使用是很常见的,接下来我们通过使用collect进行统计、求平均值、总和、最大值、最小值,更加熟悉collect的使用,并了解工具类Collectors中常用的方法

    @Test
public void test15(){
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count); // 输出结果为:6 //获取员工薪资的平均值
Double avgSalary = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);// 输出结果为:5583.333333333333 //获取员工薪资的总和
Double total = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(total); // 输出结果为:33500.0 //获取最高薪资的员工信息
Optional<Employee> maxSalary = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(maxSalary.get()); //输出结果为:Employee{name='田七', age=30, salary=8000.0, status=FREE} //获取最低薪资的员工信息
Optional<Employee> minSalary = employees.stream()
.collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(minSalary.get()); //输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY}
}

通过使用collect,对流中元素进行分组、多级分组、分区操作。

    @Test
public void test16(){
//通过员工状态进行分组
Map<Employee.Status, List<Employee>> statusListMap = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(statusListMap);
} /**
* 多级分组
*/
@Test
public void test17(){
//通过员工状态进行分组
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
if (e.getAge() < 30) {
return "青年";
} else if (e.getAge() < 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
} /**
* 分区
*/
@Test
public void test18(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
System.out.println(map);
} /**
* 将流中的元素,按照指定格式连接
*/
@Test
public void test19(){
String str = employees.stream()
.map(e -> e.getName())
.collect(Collectors.joining(","));
System.out.println(str); //输出结果为: 张三,李四,王五,王五,田七,田七
}

并行流与顺序流

并行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java8中将并行流进行了优化,我们可以很容易地对数据进行并行操作。Stream API可以声明性地通过Parallel()与sequential()在并行流与顺序流之间进行切换。

以下实例我们使用 parallelStream 来输出空字符串的数量:

    @Test
public void test20(){
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println(count); //输出结果为:2
}

Stream API应用

Java8中的Stream API可以极大提高我们的的生产力,让我们写出高效率、干净、简洁的代码。

例如:使用Java8来求两个集合的交集、差集、并集

    @Test
public void test(){ //准备两个集合
List<String> list1 = new ArrayList<String>();
list1.add("aa");
list1.add("bb");
list1.add("cc");
list1.add("dd");
list1.add("ee"); List<String> list2 = new ArrayList<String>();
list2.add("bb");
list2.add("cc");
list2.add("ff");
list2.add("gg"); // 交集
List<String> intersection = list1.stream().filter(item -> list2.contains(item)).collect(toList());
System.out.println("---交集 intersection---");
intersection.parallelStream().forEach(System.out :: println); // 差集
List<String> reduce = list2.stream().filter(item -> !list1.contains(item)).collect(toList());
System.out.println("---差集 reduce2 (list2 - list1)---");
reduce.parallelStream().forEach(System.out :: println); //并集
list1.addAll(list2);
List<String> collect = list1.stream().distinct().collect(toList());
System.out.println("并集----去重");
collect.stream().forEach(System.out::println);
}

Stream API学习笔记的更多相关文章

  1. Java 8中Stream API学习笔记

    1)函数式编程的优势和劣势分别是什么?优势:①不可变性 ②并行操作 ③执行顺序更灵活 ④代码更加简洁纯粹的函数式编程,变量具有不可变性,同一个参数不会在不同场景下得出不同的结果,因此大大增强了系统的稳 ...

  2. ASP.NET MVC Web API 学习笔记---第一个Web API程序

    http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html GetListAll /api/Contact GetListBySex ...

  3. Windows录音API学习笔记(转)

    源:Windows录音API学习笔记 Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { W ...

  4. Node.js API 学习笔记

    常用 API 学习笔记 url 函数 url.parse: 解析 url 地址 url.resolve: 向 url 地址添加或替换字段 url.format: 生成 url 地址 querystri ...

  5. Windows录音API学习笔记

    Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { WORD      wMid; 用于波形 ...

  6. 从零开始搭建.NET Core 2.0 API(学习笔记一)

    从零开始搭建.NET Core 2.0 API(学习笔记一) 一. VS 2017 新建一个项目 选择ASP.NET Core Web应用程序,再选择Web API,选择ASP.NET Core 2. ...

  7. Windows录音API学习笔记--转

    Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { WORD      wMid; 用于波形 ...

  8. TCP协议和socket API 学习笔记

    本文转载至 http://blog.chinaunix.net/uid-16979052-id-3350958.html 分类:  原文地址:TCP协议和socket API 学习笔记 作者:gilb ...

  9. Jquery API学习笔记

    学习网站 JQuery API 中文网: http://www.jquery123.com/ 学习一遍API可以更熟练的运用jquery并且拓展思路. 这里只挑选了一些我认为在开发中会用到的一些API ...

  10. Self-Host Web API 学习笔记

    ASP.NET Web API 不需要 IIS,直接使用控制台程序可以实现. 一.创建一个新的控制台程序,项目名为 HostApi 二.设置目标框架为.NET Framework 4 三.NuGet添 ...

随机推荐

  1. github.com/yuin/gopher-lua 踩坑日记

    本文主要记录下在日常开发过程中, 使用 github.com/yuin/gopher-lua 过程中需要注意的地方. 后续遇到其他的需要注意的事项再补充. 1.加载LUA_PATH环境变量 在实际开发 ...

  2. 原来你是这样的JAVA[04]-数组Arrays

    一.打印数组 Arrays类提供了打印数组元素的方法,Arrays.toString()和Arrays.deepToString(). //打印数组 System.out.println(Arrays ...

  3. Hadoop环境安装与配置

    1.基础操作系统环境安装(略) 2.JDK的安装与配置 当前各大数据软件如Hadoop等,仍然停留在Java 8上,在本实验选用的是Java 8.在自己的Linux系统中,jdk可以使用如下命令进行一 ...

  4. Redis系列之——Redis介绍安装配置

    文章目录 第一章 redis初识 1.1 Redis是什么 1.2 Redis特性(8个) 1.3 Redis单机安装 1.3.1下载安装 1.3.2三种启动方式 1.3.2.1 最简启动 1.3.2 ...

  5. .NET 数据库大数据 方案(插入、更新、删除、查询 、插入或更新)

    1.功能介绍 (需要版本5.0.45) 海量数据操作ORM性能瓶颈在实体转换上面,并且不能使用常规的Sql去实现 当列越多转换越慢,SqlSugar将转换性能做到极致,并且采用数据库最佳API 操作数 ...

  6. 在线问诊 Python、FastAPI、Neo4j — 问题咨询

    目录 查出节点 拼接节点属性 测试结果 问答演示 通过节点关系,找出对应的节点,获取节点属性值,并拼接成想要的结果. 接上节生成的CQL # 输入 question_class = {'args': ...

  7. JDK、JRE、JVM三者介绍

    概念 JDK: Java Development Kit,java开发者工具. JRE: Java Runtime Enviroment,java运行时环境. JVM: Java Virtual Ma ...

  8. CSS之transition属性

    1.鼠标移动到div中背景颜色慢慢变化(1个属性的变化) <!DOCTYPE html> <html> <head> <title></title ...

  9. 使用Blazor构建投资回报计算器

    本文由葡萄城技术团队原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 本博客中创建的投资计算器根据存入金额和回报率计算每个投资周期的特定回报 ...

  10. 【NOI 2023 春测】 游寄

    3.2 发出发通知单:9:40 3.3 旷操,把背包扔到 \(\texttt{JF}\) 底下,和 Kaguya 一起去吃早饭. 在桥下面被老班抓到了() 我用椅子给 apj 搭了一张床. Apj 给 ...