Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则 是 Stream API。
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这 是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要 Java层面去处理。
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据 结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中, 后者主要是面向 CPU,通过 CPU 实现计算。
Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
Stream 自己不会存储元素。
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
一、创建Stream
1、通过集合创建
Java8 中的 Collection 接口被扩展,提供了两个获取流 的方法:
- default Stream stream() : 返回一个顺序流
- default Stream parallelStream() : 返回一个并行流
List<Integer> list = new ArrayList<Integer>(){{
add(1);
add(2);
add(3);
add(4);
add(5);
}};
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream2 = list.parallelStream();
2、通过数组创建
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流
static Stream stream(T[] array): 返回一个流
Integer[] arr1 = new Integer[]{1,2,3,4,5};
Stream<Integer> stream3 = Arrays.stream(arr1);
重载形式,能够处理对应基本类型的数组
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array
int[] arr2 = new int[]{1,2,3,4,5};
IntStream stream4 = Arrays.stream(arr2);
double[] arr3 = new double[]{1.0,2.0,3.0,4.0,5.0};
DoubleStream stream5 = Arrays.stream(arr3);
3、通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个 流。它可以接收任意数量的参数。
public static Stream of(T... values) : 返回一个流
Stream<String> stream6 = Stream.of("a","b","c");
Stream<Integer> stream7 = Stream.of(1,2,3,4,5);
4、创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
- 迭代 public static Stream iterate(final T seed, final UnaryOperator f)
- 生成 public static Stream generate(Supplier s)
Stream<Integer> stream8 = Stream.iterate(0, x -> x + 2); // 无限流
stream8.limit(10).forEach(System.out::println);
二、Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
首先准备测试数据
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public List<Employee> getEmployees(){
return List.of(
new Employee("张三", 18, 9999.99),
new Employee("李四", 38, 5555.99),
new Employee("王五", 50, 6666.66),
new Employee("赵六", 16, 3333.33),
new Employee("田七", 8, 7777.77)
);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 重写equals和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Double.compare(salary, employee.salary) == 0 && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
1、筛选与切片
| 方法 | 描述 |
|---|---|
| distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
| filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补 |
测试
筛选出薪水大于6000的员工
List<Employee> employees = new Employee().getEmployees();
Stream<Employee> stream = employees.stream();
// 筛选出薪水大于6000的员工
stream.filter(e -> e.getSalary() > 6000).forEach(System.out::println);
Employee{name='张三', age=18, salary=9999.99}
Employee{name='王五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
查看前两个
List<Employee> employees = new Employee().getEmployees();
Stream<Employee> stream = employees.stream();
stream.limit(2).forEach(System.out::println);
跳过前3个,查看后面的
List<Employee> employees = new Employee().getEmployees();
Stream<Employee> stream = employees.stream();
stream.skip(3).forEach(System.out::println);
Employee{name='赵六', age=16, salary=3333.33}
Employee{name='田七', age=8, salary=7777.77}
去除重复的元素
需要重写equals和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Double.compare(salary, employee.salary) == 0 && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("田七", 8, 7777.77));
employees.add(new Employee("田七", 8, 7777.77));
employees.add(new Employee("田七", 8, 7777.77));
Stream<Employee> stream = employees.stream();
stream.distinct().forEach(System.out::println);
Employee{name='田七', age=8, salary=7777.77}
2、映射
| 方法 | 描述 |
|---|---|
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流 |
获取姓名长度大于等于3的员工的姓名
List<Employee> employees = new Employee().getEmployees();
// map 后得到的是姓名的List
employees.stream().map(Employee:: getName).filter(name -> name.length() >= 3).forEach(System.out::println);
3、排序
| 方法 | 描述 |
|---|---|
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
获取按年龄升序排序的员工的年龄
List<Employee> employees = new Employee().getEmployees();
// 获取年龄再排序
employees.stream().map(Employee::getAge).sorted().forEach(System.out::println);
获取按年龄升序排序的员工的信息
重写比较规则
List<Employee> employees = new Employee().getEmployees();
// 按年龄升序输出员工信息
employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);
其中Integer.compare方法如下
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
按薪水排序
List<Employee> employees = new Employee().getEmployees();
employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).forEach(System.out::println);
Employee{name='赵六', age=16, salary=3333.33}
Employee{name='李四', age=38, salary=5555.99}
Employee{name='王二五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
Employee{name='张三三', age=18, salary=9999.99}
按姓名排序
String.CASE_INSENSITIVE_ORDER 字符串不区分大小写
List<Employee> employees = new Employee().getEmployees();
employees.stream().sorted((e1, e2) -> String.CASE_INSENSITIVE_ORDER.compare(e1.getName(), e2.getName())).forEach(System.out::println);
Employee{name='Alice', age=8, salary=7777.77}
Employee{name='Bob', age=16, salary=3333.33}
Employee{name='peter', age=18, salary=9999.99}
Employee{name='sam', age=38, salary=5555.99}
Employee{name='white', age=50, salary=6666.66}
三、Stream的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例 如:List、Integer,甚至是 void 。流进行了终止操作后,不能再次使用。
1、匹配与查找
| 方法 | 描述 |
|---|---|
| allMatch(Predicate p) | 检查是否匹配所有元素 |
| anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
| noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
| findFirst() | 返回第一个元素 |
| findAny() | 返回当前流中的任意元素 |
| count() | 返回流中元素总数 |
| max(Comparator c) | 返回流中最大值,需要实现Comparator接口 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
检查所有的员工是否都年满18岁
List<Employee> employees = new Employee().getEmployees();
boolean flag = employees.stream().allMatch(e -> e.getAge() >= 18);
System.out.println(flag);
检查是否至少有员工大于50岁
List<Employee> employees = new Employee().getEmployees();
boolean flag = employees.stream().anyMatch(e -> e.getAge() >= 50);
System.out.println(flag);
判断是否所有的员工年龄都大于18岁
List<Employee> employees = new Employee().getEmployees();
// 使用noneMatch
boolean flag = employees.stream().noneMatch(e -> e.getAge() <= 18); // 作用是判断是否所有的员工年龄都大于18岁
计算员工总数
List<Employee> employees = new Employee().getEmployees();
long count = employees.stream().count();
System.out.println(count);
求最多薪水的员工
List<Employee> employees = new Employee().getEmployees();
System.out.println(employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
2、归约
| 方法 | 描述 |
|---|---|
| reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一 个值。返回 T |
| reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一 个值。返回 Optional |
求1-10的和
使用方法引用更方便简洁
List<Integer> nums= Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// lambda表达式写法
System.out.println(nums.stream().reduce(0, (x1, x2) -> x1 + x2));
// 方法调用
System.out.println(nums.stream().reduce(0, (x1, x2) -> Integer.sum(x1, x2)));
// 方法引用
System.out.println(nums.stream().reduce(0, Integer::sum));
求所有员工的薪水总和
List<Employee> employees = new Employee().getEmployees();
System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum));
Optional[33333.740000000005] 得到的是一个Optional
List<Employee> employees = new Employee().getEmployees();
System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum).get()); // 调用get方法就可以得到总薪水
3、收集
| 方法 | 描述 |
|---|---|
| collect(Collector c) | 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。 另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:
查找薪水大于6000的员工,将结果返回为一个List
List<Employee> employees = new Employee().getEmployees();
List<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).toList();
collect.forEach(System.out::println);
Employee{name='peter', age=18, salary=9999.99}
Employee{name='white', age=50, salary=6666.66}
Employee{name='Alice', age=28, salary=7777.77}
根据年龄升序排序,返回List
List<Employee> employees = new Employee().getEmployees();
List<Employee> list1 = employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).toList();
list1.forEach(System.out::println);