概述:
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象Stream流就是将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果
值得注意的是,Stream流有一些特性:
Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作Stream不保存数据,故每个Stream流只能使用一次
一、Stream流基本说明
在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)。中间操作的返回结果都是Stream,故可以多个中间操作叠加;终止操作用于返回我们最终需要的数据,只能有一个终止操作。
1.如何产生Stream流
Collection集合类的stream()方法或者parallelStream()方法产生
// 集合接口实现类直接调用方法创建流List list = new ArrayList();list.stream();list.parallelStream();// collection接口创建流Collection<String> collection = Arrays.asList("a", "b", "c");Stream<String> streamOfCollection = collection.stream();
Stream的静态方法:
// Stream.of() 产生了一个包含整型泛型的Stream流Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);// 如果创建空流,要使用empty()方法,避免为没有元素的流返回Null.Stream<Object> empty = Stream.empty();
Arrays.stream()从数组或其一部分创建
String[] arr = new String[]{"a", "b", "c"};Stream<String> streamOfArrayFull = Arrays.stream(arr);// arr数组,1-beginIndex,2-endIndexStream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
Stream.builder()构建流
Stream<String> streamBuilder = Stream.<String>builder().add("a").add("b").add("c").build();
Stream.generate()或者iterate()流
// generate() 方法需要就收一个Supplier对象作为元素生成.// 注意这个方法生成了一个无限流,也就是说没有像集合那样有长度,它是无限的// 此外,这个流它的顺序是无序的// limit(10) 限定了它的长度为10Stream<String> streamGenerated =Stream.generate(() -> "element").limit(10);
// 这个流产生的是同样的无限流,但是是有顺序的Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);
其实产生流的方法有很多,这里只列举了常见的创建流的方法,其余的不在多述。
2.Stream流的中间操作API(Intermediate)
对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符
3.Stream流的终止操作API(Termediate)
数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。
上面列举了一些Stream流中间,终止操作API,实际当中方法更多。下面会结合代码来进行举例说明
二、使用例子
准备一些数据
Student stu1 = new Student("1","xiaoming","man","20");Student stu2 = new Student("2","wangwu","woman","22");Student stu3 = new Student("3","lisi","man","24");Student stu4 = new Student("4","xiyangyang","man","26");Student stu5 = new Student("5","zhangsan","man","27");Student stu6 = new Student("6","xiaohong","woman","27");List<Student> data = new ArrayList<>();data.add(stu1);data.add(stu2);data.add(stu3);data.add(stu4);data.add(stu5);data.add(stu6);
Filter过滤
// 过滤所有的女性,注意这里的过滤指的是只获取性别为女性的学生对象List<Student> collect = data.stream().filter(student -> "woman".equals(student.getSex())).collect(Collectors.toList());System.out.println(collect);// 控制台输出// [Student{id='2', name='wangwu', sex='woman', age='22'}, Student{id='6', name='xiaohong', sex='woman', age='27'}]
// 获取性别为男性的并且年龄是24岁List<Student> collect = data.stream().filter(student -> "man".equals(student.getSex()) && "24".equals(student.getAge())).collect(Collectors.toList());System.out.println(collect);// 控制台输出// [Student{id='3', name='lisi', sex='man', age='24'}]
map类型映射
// 取出所有用户的名字// 写法1:List<String> collect = data.stream().map(student -> student.getName()).collect(Collectors.toList());// 写法2:List<String> collect = data.stream().map(Student::getName).collect(Collectors.toList());System.out.println(collect);// 控制台输出[xiaoming, wangwu, lisi, xiyangyang, zhangsan, xiaohong]
distinct去重操作
List<String> list = new ArrayList<>();list.add("apple");list.add("banana");list.add("orange");list.add("pear");list.add("dog");list.add("cat");list.add("apple");list.add("apple");list.add("apple");List<String> collect = list.stream().distinct().collect(Collectors.toList());System.out.println(collect);// 控制台输出[apple, banana, orange, pear, dog, cat]
flatMap平铺数据元素,通俗来讲,就是将两个list中的数据合到一个大的list中,并且这些元素都是展开来的
// 假设我们有一些数据List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja");List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith");List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad");List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori");List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf");List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine");List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan");List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad");List<List<String>> playersInWorldCup = new ArrayList<>();playersInWorldCup.add(teamIndia);playersInWorldCup.add(teamAustralia);playersInWorldCup.add(teamEngland);playersInWorldCup.add(teamNewZeland);playersInWorldCup.add(teamSouthAfrica);playersInWorldCup.add(teamWestIndies);playersInWorldCup.add(teamSriLanka);playersInWorldCup.add(teamPakistan);System.out.println(playersInWorldCup);// 这时控制台打印是这样的[[Virat, Dhoni, Jadeja], [Warner, Watson, Smith], [Alex, Bell, Broad], [Kane, Nathan, Vettori], [AB, Amla, Faf], [Sammy, Gayle, Narine], [Mahela, Sanga, Dilshan], [Misbah, Afridi, Shehzad]]// 我们用flatMap平铺开来List<String> collect = playersInWorldCup.stream().flatMap(plist -> plist.stream()).collect(Collectors.toList());System.out.println(collect);// 得到的结果是[Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]
skip跳过元素,limit获取n个元素
// 跳过前4个元素List<String> list = new ArrayList<>();
list.add(“apple”);
list.add(“banana”);
list.add(“orange”);
list.add(“pear”);
list.add(“dog”);
list.add(“cat”);
list.add(“apple”);
list.add(“apple”);
list.add(“apple”);
List collect = list.stream().skip(4).collect(Collectors.toList());
System.out.println(collect);
// 结果是:[dog, cat, apple, apple, apple]
```java// 只取前四个List<String> collect = list.stream().limit(4).collect(Collectors.toList());System.out.println(collect);// 结果是:[apple, banana, orange, pear]
// 另外,还有一个小技巧,skip和limit配合使用可以实现分页// limit配合skip实现分页List<String> list = new ArrayList<>();for (int i = 1; i <= 20; i++) {list.add("数据"+i);}// 每页大小int pageSize = 10;// 页码int pageIndex = 1;List<String> collect = list.stream().skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList());System.out.println(collect);// 结果:[数据1, 数据2, 数据3, 数据4, 数据5, 数据6, 数据7, 数据8, 数据9, 数据10]
sorted排序
// 实体类public class Person implements Comparable<Person>{private Integer id;private String name;private String sex;private Integer age;// get,set...@Overridepublic int compareTo(Person o) {return this.age - o.getAge();}}
// 第一种方式:sorted()方法不给参数,在实体类里实现Comparable比较接口,重新比较方法List<Person> list = new ArrayList<>();Person p1 = new Person(1,"xiaoming","man",23);Person p2 = new Person(2,"xiaohong","woman",27);Person p3 = new Person(1,"xiaolei","man",24);Person p4 = new Person(1,"xiaoli","man",26);list.add(p1);list.add(p2);list.add(p3);list.add(p4);List<Person> collect = list.stream().sorted().collect(Collectors.toList());
// 第二种方式:直接在sorted方法里给定参数List<Person> collect = list.stream().sorted((paringInt(Person::getAge))).collect(Collectors.toList());// 结果:[Person{id=1, name='xiaoming', sex='man', age=23}, Person{id=1, name='xiaolei', sex='man', age=24}, Person{id=1, name='xiaoli', sex='man', age=26}, Person{id=2, name='xiaohong', sex='woman', age=27}]
// 若要倒序,只需加上reversed方法即可List<Person> collect = list.stream().sorted((paringInt(Person::getAge)).reversed()).collect(Collectors.toList());
peek处理元素内数据
// peek()方法存在的主要目的是用调试,通过peek()方法可以看到流中的数据经过每个处理点时的状态List<Person> collect = list.stream().peek(person -> System.out.println("[person]=" + person)).collect(Collectors.toList());// 输出结果:[person]=Person{id=1, name='xiaoming', sex='man', age=23}[person]=Person{id=2, name='xiaohong', sex='woman', age=27}[person]=Person{id=1, name='xiaolei', sex='man', age=24}[person]=Person{id=1, name='xiaoli', sex='man', age=26}// 和forEach不同的是,peek是中间操作,而forEach是终止操作,终止操作只能有一个
// 除去用于调试,peek()在需要修改元素内部状态的场景也非常有用,比如我们想将所有Person的名字修改为大写List<Person> collect = list.stream().peek(person -> {person.setName(person.getName().toUpperCase());}).collect(Collectors.toList());// 输出结果:[Person{id=1, name='XIAOMING', sex='man', age=23}, Person{id=2, name='XIAOHONG', sex='woman', age=27}, Person{id=1, name='XIAOLEI', sex='man', age=24}, Person{id=1, name='XIAOLI', sex='man', age=26}]
count统计
// 统计苹果出现的次数List<String> list = new ArrayList<>();list.add("apple");list.add("banana");list.add("orange");list.add("pear");list.add("dog");list.add("cat");list.add("apple");list.add("apple");list.add("apple");long apple = list.stream().filter(s -> "apple".equals(s)).count();// 另外上面还可以替换为long apple = list.stream().filter("apple"::equals).count();System.out.println(apple);// 结果是:4
findfirst查找第一个符合条件的元素
List<Person> list = new ArrayList<>();Person p1 = new Person(1,"xiaoming","man",23);Person p2 = new Person(2,"xiaohong","woman",27);Person p3 = new Person(1,"xiaolei","man",24);Person p4 = new Person(1,"xiaoli","man",26);Person p5 = new Person(1,"xiaomeng","man",23);list.add(p1);list.add(p2);list.add(p3);list.add(p4);list.add(p5);Person person1 = list.stream().filter(person -> person.getAge() == 23).findFirst().get();System.out.println(person1);// 结果:Person{id=1, name='xiaoming', sex='man', age=23}
findAny查找任意一个符合条件的元素,实际上,findAny和findFirst返回的,都是第一个对象,在有序的集合中,他们使用起来并没有什么区别。但是在并发流或者无序集合下,并不保证返回的都是第一个元素
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);Optional<Integer> any = list.stream().filter(x -> x > 1).findAny();if (any.isPresent()) {Integer result = any.get();System.out.println(result);}// 结果:任何一个大于1的数字都有可能
*match 匹配操作
List<String> list = new ArrayList<>();list.add("apple");list.add("banana");list.add("orange");list.add("pear");list.add("dog");list.add("cat");list.add("apple");list.add("apple");list.add("apple");// anyMatch表示,在判断的条件里,匹配任意一个元素返回trueboolean b = list.stream().anyMatch(s -> "cat".equals(s));System.out.println(b);// 结果是: true// allMatch表示,判断的条件里,所有的元素都是,返回true// 结果是: falseboolean b = list.stream().allMatch(s -> "cat".equals(s));// noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true// 结果是:falseboolean b = list.stream().noneMatch(s -> "applesss".equals(s));
min,max,sum求值
List<Person> list = new ArrayList<>();Person p5 = new Person(1,"xiaomeng","man",23);Person p1 = new Person(1,"xiaoming","man",23);Person p2 = new Person(2,"xiaohong","woman",27);Person p3 = new Person(1,"xiaolei","man",24);Person p4 = new Person(1,"xiaoli","man",26);list.add(p1);list.add(p2);list.add(p3);list.add(p4);list.add(p5);// 求最大值Person person = list.stream().max(paring(Person::getAge)).get();System.out.println(person);// 结果是:Person{id=2, name='xiaohong', sex='woman', age=27}// 求最小值Person person = list.stream().min(paring(Person::getAge)).get();// 结果是:Person{id=1, name='xiaoming', sex='man', age=23}
// 当然,还可以求平均值,求和,不过得借助中间操作mapToInt// 求最大值// 结果是27int asInt = list.stream().mapToInt(Person::getAge).max().getAsInt();// 求最小值// 结果是23int asInt = list.stream().mapToInt(Person::getAge).min().getAsInt();// 求平均值// 结果是24.6double asInt = list.stream().mapToInt(Person::getAge).average().getAsDouble();// 求和// 结果是123int sum = list.stream().mapToInt(Person::getAge).sum();
reduce方法用到得较少,这里不做过多研究,请参考:/p/3b0fbcc9f24d
forEach遍历
// forEach用来遍历元素list.stream().forEach(person -> System.out.println(person));// 也可以简写为:list.stream().forEach(System.out::println);// 结果是:Person{id=1, name='xiaoming', sex='man', age=23}Person{id=2, name='xiaohong', sex='woman', age=27}Person{id=3, name='xiaolei', sex='man', age=24}Person{id=4, name='xiaoli', sex='man', age=26}Person{id=5, name='xiaomeng', sex='man', age=23}
另外还有一个forEachOrdered方法
// 主要的区别在并行流的处理上// 输出的顺序不一定(效率更高)Stream.of("AAA", "BBB", "CCC").parallel().forEach(s -> System.out.println("Output:" + s));// 输出的顺序与元素的顺序严格一致Stream.of("AAA", "BBB", "CCC").parallel().forEachOrdered(s -> System.out.println("Output:" + s));
toArray转换为数组
// 这个方法就是将数据流中的数据转换为一个数组Person[] people = list.stream().toArray(Person[]::new);// 单独用这个方法没什么意思,在idea中提示可以简化方法Person[] people = list.toArray(new Person[0]);
collect收集操作
经过前面的一系列数据处理后,需要将数据收集起来,collect就是这个收集器操作
// 提供两种不同参数的重载方法// 第一种传入3个参数<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);// 第二种传入一个Collectors类的静态方法<R, A> R collect(Collector<? super T, A, R> collector);
先看一下Collectors静态工厂类,这个类包含了很多静态方法,用起来非常方便
// toList()// 在上面的例子中已经出现了不少次,没错他就是将结果收集为List类型的集合// 这个方法的底层源码默认采用的集合类型是ArrayListList<Person> collect = list.stream().sorted().collect(Collectors.toList());
// toSet()// 将结果收集为Set集合类型,默认实现是HashSet类型,转换为Set集合时,数据会去重// 产生的数据是无序的Set<Person> collect = data.stream().limit(4).collect(Collectors.toSet());
// toCollection()// 将数据收集为集合类型,可以指定集合类型ArrayList<Person> collect = data.stream().limit(4).collect(Collectors.toCollection(ArrayList::new));
// toMap()// 将数据收集为Map类型,参数必须符合K-V类型,其中Value可以为某个字段,也可以是该对象本身// Function.identity() 返回的就是该对象本身,其源码注释有这么一句话:returns a function that always returns its input argument.意思为:始终返回其输入参数类型Map<Integer, Person> collect = data.stream().collect(Collectors.toMap(Person::getId, Function.identity()));// k为id,v为某个字段Map<Integer, String> collect = data.stream().collect(Collectors.toMap(Person::getId, Person::getName));// 打印结果:{1=xiaoming, 2=xiaohong, 3=xiaolei, 4=xiaoli, 5=xiaomeng}// 那么如果key重复的该怎么处理?这里我们假设有两个id相同Person,如果他们id相同,在转成Map的时候,取age大一个,小的将会被丢弃。List<Person> data = new ArrayList<>();Person p6 = new Person(1,"xiaoming","man",24);Person p5 = new Person(5,"xiaomeng","man",23);Person p1 = new Person(1,"xiaoming","man",23);Person p2 = new Person(2,"xiaohong","woman",27);Person p3 = new Person(3,"xiaolei","man",24);Person p4 = new Person(4,"xiaoli","man",26);data.add(p1);data.add(p2);data.add(p3);data.add(p4);data.add(p5);data.add(p6);Map<Integer, Person> collect = data.stream().collect(Collectors.toMap(Person::getId, Function.identity(), BinaryOperator.maxBy(paring(Person::getAge))));// 结果为:{1=Person{id=1, name='xiaoming', sex='man', age=24}, 2=Person{id=2, name='xiaohong', sex='woman', age=27}, 3=Person{id=3, name='xiaolei', sex='man', age=24}, 4=Person{id=4, name='xiaoli', sex='man', age=26}, 5=Person{id=5, name='xiaomeng', sex='man', age=23}}