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,Redis等,而这些NoSQL的数据就需要Java层面去处理。
那么问题来了:
对数据集进行处理不是可以用集合相关的方法吗,两者有何不同?
Stream 和 Collection 集合的区别:
Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
Stream到底是什么?
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream操作的三个步骤
- 创建Stream
- 中间操作。一个中间操作链,对数据源的数据进行操作
- 终止操作。一旦执行终止操作,就执行中间操作链,并产生结果。之后,该Stream不能再被使用。
Stream的创建方式
Stream的创建主要有四种方式。
- 通过集合创建(常用)
- 通过数组创建
- 通过Stream的of()方法创建
- 通过Stream静态方法创建无限流
通过集合创建Stream
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
// 返回一个顺序流,单线程按顺序处理流。 default Streamstream() { return StreamSupport.stream(spliterator(), false); } // 返回一个并行流,把一个内容分成很多个数据块,并用不同的线程分别处理每个数据块的流。 default Stream parallelStream() { return StreamSupport.stream(spliterator(), true); }
使用示例:
@Test
public void test01() {
//通过集合创建
List list = new ArrayList<>();
//default Stream stream()返回一个顺序流
Stream stream = list.stream();
//default Stream parallelStream()返回一个并行流
Stream stringStream = list.parallelStream();
}
通过数组创建Stream
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
public staticStream stream(T[] array) { return stream(array, 0, array.length); } public static IntStream stream(int[] array) { return stream(array, 0, array.length); }
使用示例:
@Test
public void test02() {
//通过数组创建
int[] arr = new int[]{1, 2, 3, 4};
//public static IntStream stream(int[] array)
IntStream stream = Arrays.stream(arr);
Stream stringStream = Arrays.stream(new String[]{""});
}
通过Stream的of()方法创建Stream
调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static Stream of(T... values) {
return Arrays.stream(values);
}
使用示例:
@Test
public void test03() {
//通过Stream的of创建
Stream integerStream = Stream.of(1, 2, 3, 4, 5);
}
通过Stream静态方法创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate()创建无限流。
// 给定一个初始值seed,一个操作元素的方式f,通过迭代器不断执行f生产无限流 public staticStream iterate(final T seed, final UnaryOperator f) // 自定义一个生成策略,产生无限流 public static Stream generate(Supplier s)
使用示例:
@Test
public void test04() {
//创建无限流
//迭代器,public static Stream iterate(final T seed, final UnaryOperator f)
//生成偶数的流,从0开始.0,2,4,6...
Stream iterate = Stream.iterate(0, i -> i + 2);
//生成public static Stream generate(Supplier s),自定义生产策略
//生成随机数流
Stream generate = Stream.generate(Math::random);
}
Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则任何中间操作不会执行!而在终止操作时一次性全部处理,称为“惰性求值”。这也符合了Stream重在计算的宗旨,如果一个计算过程(中间操作)产生的结果没有地方需要,当然没有必须要执行啦。而有终止操作就说明有地方需要使用该计算过程产生的结果集。
1.筛选与切片
| 方 法 | 描述 |
|---|---|
| filter(Predicate p) | 过滤, 从流中排除某些元素,满足Predicate条件的留下 |
| distinct() | 筛选,通过流所生成元素的 equals() 去除重复元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit互补 |
使用示例:
@Test
public void test01(){
List list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.add(500);
list.add(600);
list.add(600);
list.add(600);
Stream integerStream = list.stream();
//filter筛选某些数据
integerStream.filter(item -> item >= 200).forEach(item -> System.out.print(item + ","));
System.out.println("n**********************************");
//limit截断流
list.stream().limit(3).forEach(item -> System.out.print(item + ","));
System.out.println("n**********************************");
//skip跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补
list.stream().skip(3).forEach(item -> System.out.print(item + ","));
System.out.println("n**********************************");
//distinct()筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素
list.stream().distinct().forEach(item -> System.out.print(item + ","));
}
forEach属于终止操作。
2.映 射
| 方法 | 描述 |
|---|---|
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
mapToDouble,mapToInt,mapToLong只是限定了返回值的类型。
关于flatMap的作用,类似集合的 add方法和addAll方法。addAll会将集合中的每个元素都拿出来添加进新集合,而不是将集合作为一个元素整个添加。
使用示例:
@Test
public void test03() {
//map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
//先转成大写再取长度小于5的
list.stream().map(String::toUpperCase).filter(item -> item.length() < 5).forEach(item -> System.out.print(item + ","));
List> list1 = new ArrayList<>();
List list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
List list3 = new ArrayList<>();
list3.add(4);
list3.add(5);
list3.add(6);
list1.add(list2);
list1.add(list3);
//flatMap将流中的每个值都转换成新流,然后将这些流组装成一个流
System.out.println("n*****************");
//Stream> streamStream = list1.stream().map(List::stream);
list1.stream().map(List::stream).forEach(integerStream -> {
integerStream.forEach(item -> System.out.print(item + ","));
});
System.out.println("n*****************");
list1.stream().flatMap(List::stream).forEach(item -> System.out.print(item + ","));
}
- 排序
| 方法 | 描述 |
|---|---|
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
使用示例:
@Data
@AllArgsConstructor
class Student{
private Integer age;
private String name;
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
}
@Test
public void test06() {
// sorted自然排序
List integerList = Arrays.asList(1, 2, -1, 22, 32, 0, 8);
integerList.stream().sorted().forEach(item -> System.out.print(item + ", "));
System.out.println();
List students = new ArrayList<>();
students.add(new Student(11,"zhangsan"));
students.add(new Student(14,"lisi"));
students.add(new Student(18,"wangwu"));
students.add(new Student(22,"zhaoliu"));
//调用sorted()方法会抛异常
// java.lang.ClassCastException: com.chunni.streamapi.StreamAPITest02$Student cannot be cast to java.lang.Comparable
//students.stream().sorted().forEach(System.out::println);
students.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
}
关于sorted()和sorted(Comparator com)其实本质都一样,要求排序的类实现compare方法。只不过一些Java提供的包装类本来就实现了Comparable,所以不用我们再去实现。
Stream 的终止操作
终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。流进行了终止操作后,不能再次使用。
1.匹配与查找
| 方法 | 描述 |
|---|---|
| allMatch(Predicate p) | 检查是否匹配所有元素 |
| anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
| noneMatch(Predicatep) | 检查是否没有匹配的元素 |
| findFirst() | 返回第一个元素 |
| findAny() | 返回当前流中的任意元素 |
| count() | 返回流中元素总数 |
| max(Comparator c) | 返回流中最大值 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代,不需要显示写for循环,只需要写需要对每个元素的操作) |
使用示例:
@Test
public void test07() {
List students = new ArrayList<>();
students.add(new Student(11,"zhangsan"));
students.add(new Student(14,"lisi"));
students.add(new Student(18,"wangwu"));
students.add(new Student(22,"zhaoliu"));
// 是否所有学生的年龄都大于18岁
boolean allMatch = students.stream().allMatch(student -> student.getAge() > 18);
System.out.println(allMatch);
// 是否存在学生的年龄大于18岁
boolean anyMatch = students.stream().anyMatch(student -> student.getAge() > 18);
System.out.println(anyMatch);
// 是否没有学生的年龄大于18岁(学生的年龄是否全小于等于18)
boolean noneMatch = students.stream().noneMatch(student -> student.getAge() > 18);
System.out.println(noneMatch);
// 获取第一个学生的信息
Optional first = students.stream().findFirst();
System.out.println(first);
// 随机获取一个学生的信息
Optional any = students.stream().findAny();
System.out.println(any);
// 获取18岁以上学生总数
long count = students.stream().filter(student -> student.getAge() > 18).count();
System.out.println(count);
// 获取年龄最大的学生的年龄
Optional max = students.stream().map(Student::getAge).max(Integer::compareTo);
System.out.println(max);
// 获取年龄最小的学生的年龄
Optional min = students.stream().map(Student::getAge).min(Integer::compareTo);
System.out.println(min);
// 遍历打印学生姓名
students.stream().map(Student::getName).forEach(name -> System.out.print(name + ", "));
}
2.归约
reduce 被称为归约,它表示另外一种映射方式,通常完成聚合的工作
| 方法 |
|---|
| T reduce(T identity, BinaryOperator< T > accumulator); |
| Optional< T > reduce(BinaryOperator< T > accumulator); |
| < U > U reduce(U identity, BiFunction accumulator, BinaryOperator< U > combiner); |
Optional
注释中给的说明:
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
//先赋初值
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
这里accumulator.apply()代表元素之间进行的操作。比如将所有元素求和。
T reduce(T identity, BinaryOperator
下面是注释中的说明:
// 给result赋初始值,不用再从流中获取
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
针对上面两个方法的示例:
//所有元素求和 Listlist = Arrays.asList(1, 1, 1, 1, 1, 1); //执行过程: ((((1+1)+1)+1)+1)+1)+1 Optional reduce = list.stream().reduce(Integer::sum); // 给初始值6 // 执行过程:((((((6+1)+1)+1)+1)+1)+1)+1 Integer reduce1 = list.stream().reduce(6, Integer::sum); System.out.println(reduce); System.out.println(reduce1);
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);
以下是官方API文档的说明:
Here, the identity element is both an initial seed value for the reduction and a default result if there are no input elements. The accumulator function takes a partial result and the next element, and produces a new partial result. The combiner function combines two partial results to produce a new partial result. (The combiner is necessary in parallel reductions, where the input is partitioned, a partial accumulation computed for each partition, and then the partial results are combined to produce a final result.)
意思是:identity元素是初始值。accumulator()接受一个部分结果和下一个元素,并产生一个新的部分结果。combiner函数将两个部分结果组合起来,产生一个新的部分结果。第三个参数是为了处理并行流,将多个任务的结果合并。
并行流使用这个方法需要满足两个条件:
1.u = combiner(identity, u) 2.combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
违反其中任何一条都会出现问题。
从上面两个方法中也能看出,reduce设计来就是为了让流中的元素不断重复相同的操作将元素结合起来形成一个结果。那么,通过官方文档中的说明来看combiner的出现并不是为了改变原来的结果,而是提供一种新的计算方式。 以accumulator和combiner都为加法为例(combiner类似加法结合律,可以任意组合中间结果,先算哪部分都可以),在对并行流进行处理时,会有多个线程,所以如果identity不满足上述条件就会出现:identity参与了多次计算,计算结果和处理顺序流不相同,而只有identity=0时才能满足。
关于reduce细节参见:https://blog.csdn.net/qq_38200877/article/details/121230706
使用示例:
//求和 Listlist = Arrays.asList(1, 1, 1, 1, 1, 1); System.out.println(list.stream().reduce(1, Integer::sum, Integer::sum)); //identity参与6次运算,违反条件 System.out.println(list.parallelStream().reduce(1, Integer::sum, Integer::sum)); // 初始值为0时,满足条件 System.out.println(list.parallelStream().reduce(0, Integer::sum, Integer::sum));
3.收集(java.util.stream.Collectors 类相关操作)
| 方法 | 描述 |
|---|---|
| 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。另外, java.util.stream.Collectors 类提供了很多静态方法,可以方便地创建常见收集器实例。
| 方法 | 返回值 | 描述 |
|---|---|---|
| toList | List< T > | 把流中元素收集到List |
| toSet | Set< T > | 把流中元素收集到Set |
| toCollection | Collection< T > | 把流中元素收集到创建的集合 |
| counting | Long | 计算流中元素的个数 |
| summingInt | Integer | 对流中元素的整数属性求和 |
| averagingInt | Double | 计算流中元素Integer属性的平均值 |
| summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。(总数,求和,最小值,最大值,平均值) |
| joining | String | 连接流中每个字符串 |
| maxBy | Optional< T > | 根据比较器选择最大值 |
| minBy | Optional< T > | 根据比较器选择最小值 |
| groupingBy | Map | 根据某属性值对流分组,属性为K,结果为V |
| partitioningBy | Map | 根据true或false进行分区 |
使用示例:
@Test
public void test09() {
List students = new ArrayList<>();
students.add(new Student(11,"zhangsan"));
students.add(new Student(14,"lisi"));
students.add(new Student(18,"wangwu"));
students.add(new Student(22,"zhaoliu"));
students.add(new Student(22,"john"));
// 获取年龄大于18岁的学生列表
ArrayList students1 = students.stream().filter(student -> student.getAge() > 18)
.collect(Collectors.toCollection(ArrayList::new));
List students2 = students.stream().filter(student -> student.getAge() > 18)
.collect(Collectors.toList());
Set students3 = students.stream().filter(student -> student.getAge() > 18).collect(Collectors.toSet());
// 获取年龄大于18岁的学生人数
Long aLong = students.stream().filter(student -> student.getAge() > 18).collect(Collectors.counting());
System.out.println(aLong);
// 对学生年龄求和
Integer ages = students.stream().collect(Collectors.summingInt(Student::getAge));
System.out.println(ages);
// 对学生的年龄信息进行统计:总数,求和,最小值,最大值,平均值
IntSummaryStatistics intSummaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println(intSummaryStatistics);
// 对学生的年龄求平均值
Double aDouble = students.stream().collect(Collectors.averagingInt(Student::getAge));
System.out.println(aDouble);
// 将学生的名字连接起来
String s = students.stream().map(Student::getName).collect(Collectors.joining());
// 将学生的名字用逗号连接起来
String s1 = students.stream().map(Student::getName).collect(Collectors.joining(","));
// 将学生的名字用逗号连接起来,在首位加上方括号
String s2 = students.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
System.out.println(s);
System.out.println(s1);
System.out.println(s2);
// 根据学生年龄分组
Map> integerListMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
// 根据学生年龄分组,然后将学生年龄和名字的列表组成map
Map> integerSetMap = students.stream()
.collect(Collectors.groupingBy(Student::getAge, Collectors.mapping(Student::getName, Collectors.toSet())));
// 根据学生年龄分组,然后将学生年龄和名字的列表组成指定类型的map
TreeMap> integerSetTreeMap = students.stream()
.collect(Collectors.groupingBy(Student::getAge, TreeMap::new, Collectors.mapping(Student::getName, Collectors.toSet())));
// 查出年龄最大和最小的学生信息
Optional optionalStudent = students.stream().collect(Collectors.maxBy(Comparator.comparingInt(Student::getAge)));
Optional optionalStudent1 = students.stream().collect(Collectors.minBy(Comparator.comparingInt(Student::getAge)));
System.out.println(optionalStudent);
System.out.println(optionalStudent1);
}



