Stream流
为什么使用Stream流?
1.Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。相当于高级版的Iterator。
2.极大的提高编程效率和程序可读性。
3.同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
stream流的概念:
Sequence of elements(元素序列)
:简单来说,就是我们操作的集合中的所有元素source(数据源)
:Stream流的作用就是操作数据,那么source 就是为Stream提供可操作的源数据
(一般,集合、数组或I/OI/O resources 都可以成为Stream的source )Data processing operations(数据处理操作)
:上面菜单程序代码中出现的filter
、sorted、map、collect,以及我们后来会用到的
reduce、find、match`等都属于Stream 的一些操作数据的方法接口。这些操作可以顺序进行,也可以并行执行。Pipelining(管道、流水线)
:Stream对数据的操作类似数据库查询,也像电子厂的生产流线一样,Stream的每一个中间操作(后面解释什么是中间操作)比如上面的filter、sorted、map,每一步都会返回一个新的流,这些操作全部连起来就是想是一个工厂得生产流水线, like this:Internal iteration(内部迭代)
:Stream API 实现了对数据迭代的封装,不用你再像操作集合一样,手动写for循环显示迭代数据。
stream操作步骤
- 创建Stream:一个数据源(如:集合,数组,
Map不可以作为数据源
)获取一个流 - 中间操作:一个中间操作链,对数据源的数据进行操作处理(过滤、映射...)
- 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果(一旦执行终止操作,就执行中间操作链,并产生结果。之后的,不会再被使用)
获取Stream流的方式
- 通过集合
- 通过数组
- 通过Stream的of()
- 通过无限流
* java.util.stream.Stream<T>是Java 8 新加入的最常用的流接口。(这并不是一个函数式接口。) * 获取一个流非常简单,有以下两种常用的方式: - 所有的Collection集合都可以通过stream默认方法获取流; default Stream<T> stream () - Stream 接口的静态方法of可以获取数组对应的流。 static <T> Stream <T> of (T...values) 参数是一个可变参数,那么我们就可以传递一个数组
List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 2000, 18)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 2371, 55)); add(new Person("Floyd", "Donny", "Java programmer", "male", 3322, 25)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 35020, 15)); add(new Person("Vere", "Hervey", "Java programmer", "male", 2272, 25)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 2057, 87)); add(new Person("Shawn", "Randall", "Java programmer", "male", 3120, 99)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 345, 25)); add(new Person("Palmer", "Dene", "Java programmer", "male", 3375, 14)); add(new Person("Addison", "Pam", "Java programmer", "female", 3426, 20)); add(new Person("Addison", "Pam", "Java programmer", "female", 3422, 20)); add(new Person("Addison", "Pam", "Java programmer", "female", 3429, 20)); // add(new Person("Addison", "Pam", "Java programmer", null, 3422, 20)); } };
package com.zy.stream.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder(toBuilder = true) @AllArgsConstructor @NoArgsConstructor public class Person { private String firstName, lastName, job, gender; private int salary,age; }
通过集合创建Stream流
//1.1 通过集合创建Stream流------> default Stream<E> stream:返回一个顺序流 Stream<Person> stream=javaProgrammers.stream(); //1.2 通过集合创建Stream流------> default Stream<E> parallelStream:返回一个并行流 Stream<Person> parallelStream = javaProgrammers.parallelStream();
我们通过集合javaProgrammers
创建了Stream流,那么为什么会返回一个流呢?
我们看Collection
集合类的源码:
//1.1 当集合集合调用stream()时,return一个Stream<E> default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } //1.2 同理 default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); }
通过数组创建Stream流
//2.1 通过数组创建Stream流-------> 调用Arrays类的static <T> Stream<T> Stream<T[] array> :返回一个流 Person[] arrPerson = new Person[]{javaProgrammers.get(0),javaProgrammers.get(1)}; Stream<Person> streamObj = Arrays.stream(arrPerson);
通过静态类Arrays
调用Stream中的静态Stream
方法,即可返回一个流。
静态Stream方法源码:
//入参Array数组,返参 <T> Stream<T>流 public static <T> Stream<T> stream(T[] array) { return stream(array, 0, array.length); } public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) { return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false); }
通过Stream的of方法创建流
//3.1 通过of创建Stream Stream<Integer> streamOf = Stream.of(1, 2, 3, 4, 5, 6); Stream<Person> streamOfs = Stream.of(javaProgrammers.get(0),javaProgrammers.get(1));
of方法源码:
@SafeVarargs @SuppressWarnings("varargs") // Creating a stream from an array is safe public static<T> Stream<T> of(T... values) { //通过of源码我们可以看到,其实of方法任然是使用的是数组的创建的方式,只是外部有包了一层 return Arrays.stream(values); }
通过无线流的创建Stream流
//4.1 通过无限流的方式创建Stream流 /*4.1.1 迭代--->*public static<T> Stream<T> iterate(final T seed,final UnaruOperator<T> f) * 遍历前10个偶数 * 无限流,无限反复操作使用,所有一般都会配合limit使用 */ Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println); //seed是起始数值,limit代表循环前10次 //4.1.2 生成----》public static<T> Stream<T> generate(Supplier<T> s) Stream.generate(Math::random).limit(10).forEach(System.out::println);
(1)Java8 中的 Collection 接口被扩展,提供了 两个获取流的方法:
default Stream stream() : 返回一个顺序流
default Stream parallelStream() : 返回一个并行流
(2)Java8 中的 Arrays 的静态方法 stream() 可 以获取数组流:
static Stream stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
(3)可以使用静态方法 Stream.of(), 通过显示值 创建一个流。它可以接收任意数量的参数。
public static Stream of(T... values) : 返回一个流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
1.迭代
public static Stream iterate(final T seed, final UnaryOperator f)
2.生成
public static Stream generate(Supplier s)
1.4 Stream流中的常用方法
中间操作有:
- 筛选和切片
⚪filter
(Predicate p)–接受Lambda,从流中排除某些元素
⚪limit(n)
–截断流,使其元素不超过给定数量
⚪skip(n)
–跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
⚪distinct
–筛选,通过流所生成的元素的 hashCode() 和 equals() 去除重复元素 - 映射
⚪map(Function f)
–接受一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
⚪flatMap(Function f)
–接受一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有流连成一个流 - 排序
⚪sorted()–自然排序,产生一个新流,其中按自然顺序排序
⚪sorted(Comparator com)–定制排序,产生一个新流,其中按比较器顺序排序
下面我们通过代码实现来分析这些方法:
forEach() 用于遍历流中的数据
* void forEach(Consumer<? super T> action); 该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。 Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据 * 简单记: forEach方法,用来遍历流中的数据 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
import java.util.stream.Stream; public class Demo02Stream_forEach { public static void main(String[] args) { //获取一个Stream流 Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七"); //使用Stream流中的方法forEach对Stream流中的数据进行遍历 /*stream.forEach((String name)->{ System.out.println(name); });*/ //优化Lambda表达式 stream.forEach(name->System.out.println(name)); } } 打印结果: 张三 李四 王五 赵六 田七
filter() 用于过滤流中的数据
Stream<T> filter(Predicate<? super T> predicate); filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤 * Predicate中的抽象方法: boolean test(T t);
import java.util.stream.Stream; public class Demo03Stream_filter { public static void main(String[] args){ //创建一个Stream流 Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌"); //使用filter()对Stream流中的元素进行过滤,只要姓张的人 Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("张");}); //遍历stream2流,打印 stream2.forEach(name-> System.out.println(name)); } } 打印结果: 张三丰 张无忌
map() 用于将流中的元素映射到另一个流中
* <R> String<R> map(Function<? super T, ? extends R> mapper); 该接口需要一个Function函数式接口,可以将当前流中的T类型的数据转换为另一种R类型的流。 * Function中的抽象方法: R apply(T t);
import java.util.stream.Stream; public class Demo04Stream_map { public static void main(String[] args) { //获取一个String类型的Stream流 Stream<String> stream = Stream.of("1", "2", "3", "4", "5"); //使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数 Stream<Integer> stream2 = stream.map((String s)->{ return Integer.parseInt(s); }); //遍历Stream流 stream2.forEach(i-> System.out.println(i)); } } 打印结果: 1 2 3 4 5
count()统计流中数据元素的个数
* Long count();
count方法是一个终结方法,返回值是一个long类型的整数
所以不能再继续调用Stream流中的其他方法了
import java.util.ArrayList; import java.util.stream.Stream; public class Demo05Stream_count { public static void main(String[] args) { //获取一个Stream流 ArrayList<Integer> list = new ArrayList<>(); list.add(0); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); Stream<Integer> stream = list.stream(); long count = stream.count(); System.out.println(count); //8 } }
limit() 用于截取流中的元素,只取前n个
g maxSize); - 参数是一个long型,如果几个当前长度大于参数则进行截取;否则不进行操作 - limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
import java.util.stream.Stream; public class Demo06Stream_limit { public static void main(String[] args) { //获取一个Stream流 String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"}; Stream<String> stream = Stream.of(arr); //使用limit方法对Stream流中的元素进行截取,只要前3个元素 Stream<String> stream2 = stream.limit(3); //遍历stream2流 stream2.forEach(name-> System.out.println(name)); } } 打印结果: 美羊羊 喜羊羊 懒羊羊
skip 跳过Stream流中的前几个元素,截取后几个元素
* Stream流中的常用方法_skip:用于跳过元素 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流; * Stream<T> skip(long n); 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
import java.util.stream.Stream; public class Demo07Stream_skip { public static void main(String[] args) { //获取一个Stream流 String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"}; Stream<String> stream = Stream.of(arr); //使用skip方法跳过前3个元素 Stream<String> stream2 = stream.skip(3); //遍历stream2流 stream2.forEach(name-> System.out.println(name)); } } 打印结果: 灰太狼 红太狼
concat 用于合并两个Stream流为一个Stream流
* static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 用于把流组合到一起 如果有两个流,希望合并成为一个流,那么可以使用Stream接口中的经他方法concat
import java.util.stream.Stream; public class Demo08Stream_concat { public static void main(String[] args) { //创建一个Stream流stream1 Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌"); //获取一个Stream流stream2 String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"}; Stream<String> stream2 = Stream.of(arr); //把以上两个流组合成一个流 Stream<String> concat = Stream.concat(stream1, stream2); //遍历concat流 concat.forEach(name-> System.out.println(name)); } }
distinct()方法*
distinct()返回由该流的不同元素组成的流。distinct()是Stream接口的方法。distinct()使用hashCode()和equals()方法来获取不同的元素
。我们的类必须实现hashCode()和equals()。如果distinct()正在处理有序流,那么对于重复元素,将保留以遭遇顺序首先出现的元素,并且以这种方式选择不同元素是稳定的。在无序流的情况下,不同元素的选择不一定是稳定的,是可以改变的。distinct()执行有状态的中间操作。在有序流的并行流的情况下,保持distinct()的稳定性是需要很高的代价的,因为它需要大量的缓冲开销。如果我们不需要保持遭遇顺序的一致性,那么我们应该可以使用通过BaseStream.unordered()方法实现的无序流。
//1.4 `distinct`–筛选,通过流所生成的元素的 hashCode() 和 equals() 去除重复元素 //1.4.1 distinct取出集合中对象信息完全重复的对象 javaProgrammers.stream().distinct().forEach(System.out::println);
排序
自然排序的对象是需要实现Comparable接口的
sorted()方法
@Test public void testStore(){ //3.1 sorted()–自然排序,产生一个新流,其中按自然顺序排序 sorted()参数可选,有两个重载一个有参Comparator,一个无参 List<Integer> list = Arrays.asList(12, 43, 65, 3, 4, 0,01, -98); list.stream().sorted().forEach(System.out::println); //3.1.1 抛异常,原因:Person没有实现Comparable接口 //javaProgrammers.stream().sorted().forEach(System.out::println); //3.2 sorted(Comparator com)–定制排序,产生一个新流,其中按比较器顺序排序,根据条件排序 //多集合中对象根据年龄进行排序 javaProgrammers.stream().sorted((e1,e2)->Integer.compare(e1.getAge(),e2.getAge())).forEach(System.out::println); }
Stream流的终止方法:
终止操作有:
- 匹配与查找
⚪allMatch(Predicate p):检查是否匹配所有元素
⚪anyMatch(Predicate p):检查是否至少匹配一个元素
⚪noneMatch(Predicate p):检查是否没有匹配的元素
⚪findFirst():返回第一个元素
⚪findAny():返回当前流中的任意元素
⚪count():返回流中元素的总数
⚪max(Comparator c):返回流中最大值
⚪min(Comparator c):返回流中最小值
⚪forEach(Consumer c):内部迭代 - 规约
⚪reduce(T identity,BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回T
⚪reduce(BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回Optional - 收集
⚪collect(Collector c):将流转换成其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
匹配与查找
@Test public void testMatchAndFind() { /** * 1. 匹配与查找 * allMatch(Predicate p):检查是否匹配所有元素 * anyMatch(Predicate p):检查是否至少匹配一个元素 * noneMatch(Predicate p):检查是否没有匹配的元素 * findFirst():返回第一个元素 * findAny():返回当前流中的任意元素 * count():返回流中元素的总数 * max(Comparator c):返回流中最大值 * min(Comparator c):返回流中最小值 * forEach(Consumer c):内部迭代 **/ //1.1 allMatch()相当于制定一个规则,将集合中的对象一一与规则进行对象,判断是否所有的集合对象都符合该规则, true/false boolean allMatch = javaProgrammers.stream().allMatch(e -> e.getAge()>18); System.out.println("测试Stream流的终止操作----allMatch()--->"+allMatch); //1.2 anyMatch(Predicate p):检查是否至少匹配一个元素 类似于多选一即可 boolean anyMatch = javaProgrammers.stream().anyMatch(e -> e.getSalary()>1000); System.out.println("检查是否至少匹配一个元素----anyMatch()--->"+anyMatch); //1.3 noneMatch(Predicate p):检查是否没有匹配的元素 没有返回true,反之false boolean noneMatch = javaProgrammers.stream().noneMatch(e -> e.getSalary() > 10000); System.out.println("检查是否没有匹配的元素----noneMatch()--->"+noneMatch); //1.4 findFirst():返回第一个元素 Person person = javaProgrammers.stream().findFirst().get(); System.out.println("返回第一个元素----findFirst()--->"+person.toString()); for (int i = 0; i <1000 ; i++) { //1.5 findAny():返回当前流中的任意元素 Optional<Person> AnyPerson = javaProgrammers.stream().findAny(); //System.out.println("返回当前流中的任意元素----findAny()--->"+AnyPerson.toString()+"--------->"+i); } //1.6 count():返回流中元素的总数 long count = javaProgrammers.stream().count(); System.out.println("返回流中元素的总数----count()--->"+count); //1.7 max(Comparator c):返回流中最大值 Optional<T> max(Comparator<? super T> comparator); Person maxPersonSalary = javaProgrammers.stream().max(Comparator.comparing(Person::getSalary)).get(); System.out.println("返回流中最大值----max()--->"+maxPersonSalary); //1.8 min(Comparator c):返回流中最小值 Person minPersonSalary = javaProgrammers.stream().min(Comparator.comparing(Person::getSalary)).get(); System.out.println("返回流中最小值----max()--->"+minPersonSalary); }
规约
public void testStatute(){ /* * 2. **规约** * reduce(T identity,BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回T * reduce(BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回Optional * * reduce()这个方法经常用到很方便,也更总要,我会再详细的分析总结,这里只做简单认识即可。 */ //2.1 reduce(T identity,BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回T //2.1.1 练习1:计算1-10的自然数的和 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Integer sum = list.stream().reduce(0, Integer::sum); System.out.println("计算1-10的自然数的和-------reduce(T identity,BinaryOperator accumulator)------>"+sum); //2.2 reduce(BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回Optional Integer integers = javaProgrammers.stream().map(Person::getSalary).reduce((e1, e2) -> e1 + e2).get();//lambda表达式 Integer integer = javaProgrammers.stream().map(Person::getSalary).reduce(Integer::sum).get();//使用引用 System.out.println("工资和-------reduce(BinaryOperator accumulator)使用引用------>"+integer); System.out.println("工资和-------reduce(BinaryOperator accumulator)lambda表达式------>"+integers); }
收集
Collector
接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map
)Collector
需要使用Collectors
提供实例。
@Test public void testSalary(){ /* * 3. **收集** * collect(Collector c):将流转换成其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法 * Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。 * Collector需要使用Collectors提供实例。另外, Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下: ⚪toList:返回类型List< T>,作用是把流中元素收集到List ⚪toSet:返回类型Set< T>,作用是把流中元素收集到Set ⚪toCollection:返回类型Collection< T>,作用是把流中元素收集到创建的集合 * */ //3.1 collect(Collector c):将流转换成其他形式。返回一个set System.out.println("将流转换成其他形式。返回一个set-------toSet()------>:"); javaProgrammers.stream().filter(e -> e.getSalary() > 3000).collect(Collectors.toSet()).forEach(System.out::println); //3.2 collect(Collector c):将流转换成其他形式。返回一个list System.out.println("将流转换成其他形式。返回一个list-------toList()------>:"); javaProgrammers.stream().filter(e -> e.getSalary() > 3000).limit(2).collect(Collectors.toList()).forEach(System.out::println); //3.2 collect(Collector c):将流转换成其他形式。返回一个map System.out.println("将流转换成其他形式。返回一个map-------toMap()------>:"); javaProgrammers.stream().filter(e -> !e.getFirstName().equals("测试")) // 注意:key不能重复 toMap()参数一:key 参数二:value 参数三:对key值进行去重,当有重复的key,map中保留第一条重复数据 .collect(Collectors.toMap(Person::getAge,person -> person,(key1, key2) -> key1)) //value 为对象 student -> student jdk1.8返回当前对象,也可以为对象的属性 .forEach((key, value) -> System.out.println("key--"+key+" value--"+value.toString())); }
Stream流的生命周期
同一个流只能遍历一次,遍历完后,这个流就已经被消费掉了。你如果还需要在遍历,可以从原始数据源那里再获得一个新的流来重新遍历一遍。
比如:
List<Person> listPerson = new ArrayList<Person>() { { //元素序列-----翻译过来就是集合中的所有元素对象 add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 2000, 18)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 2371, 55)); add(new Person("Floyd", "Donny", "Java programmer", "male", 3322, 25)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 35020, 15)); add(new Person("Vere", "Hervey", "Java programmer", "male", 2272, 25)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 2057, 87)); add(new Person("Shawn", "Randall", "Java programmer", "male", 3120, 99)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 345, 25)); add(new Person("Palmer", "Dene", "Java programmer", "male", 3375, 14)); add(new Person("Addison", "Pam", "Java programmer", "female", 3426, 20)); } }; //获取集合中年龄大于三十的人的名字,返回集合 List<String> reList = listPerson .stream()//创建Stream实例 .filter(e -> e.getAge() > 30).map(e -> e.getFirstName())//中间操作 .collect(Collectors.toList());//终端操作 Stream<Person> stream1 = listPerson.stream(); stream1.forEach(System.out::print); stream1.forEach(System.out::print);
同一个流 s 被两次用于forEach的终端操作,此时控制台报错,提示Stream流已被操作或者关闭。
但是当我们从原始数据源集合中那里再获得一个新的流在操作就可以:
同一个流 s 被两次用于forEach的终端操作,此时控制台报错,提示Stream流已被操作或者关闭。
但是当我们从原始数据源集合中那里再获得一个新的流在操作就可以:
//获取集合中年龄大于三十的人的名字,返回集合 List<String> reList = listPerson .stream()//创建Stream实例 .filter(e -> e.getAge() > 30).map(e -> e.getFirstName())//中间操作 .collect(Collectors.toList());//终端操作 Stream<Person> stream1 = listPerson.stream(); stream1.forEach(System.out::print); Stream<Person> stream2 = listPerson.stream(); stream2.forEach(System.out::print);
Stream在有些情况下,可能会比传统for慢一些,但是其他情况下还是没啥区别的。
本文来自博客园,作者:King-DA,转载请注明原文链接:https://www.cnblogs.com/qingmuchuanqi48/p/14131064.html