概述:

  • 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-endIndex
Stream<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) 限定了它的长度为10
Stream<String> streamGenerated =Stream.generate(() -> "element").limit(10);
// 这个流产生的是同样的无限流,但是是有顺序的
Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);

其实产生流的方法有很多,这里只列举了常见的创建流的方法,其余的不在多述。

2.Stream流的中间操作API(Intermediate )

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符

方法

说明

filter

过滤操作,把不想要的数据过滤掉

map

转换操作,有mapToInt(),mapToDouble(),mapToLong()方法,有点类似于类型转换

flatMap

平铺操作,也就是将一些数据展开,比如把int[]{2,3,4}展开变为2,3,4

limit

限流操作,比如数据流中有10个 我只要出前3个就可以使用

distinct

去重操作,对重复元素去重,底层使用了equals方法

peek

挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等

skip

跳过操作,跳过某些元素不做处理

sorted

排序操作,对元素排序,前提是实现Comparable接口

3.Stream流的终止操作API(Termediate)

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

方法

说明

collect

收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors

count

统计操作,统计最终的数据个数

findFirst

查找第一个匹配的元素

findAny

查找任何一个匹配的元素

noneMatch

-

allMatch

三个Match用来匹配数据流中是否符合条件的元素,返回布尔值

anyMatch

-

max,min,sum

求最大值,最小值,求和操作

reduce

规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce

forEach

forEach、forEachOrdered 遍历操作

toArray

数组操作,将数据流的元素转换成数组

上面列举了一些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>> playersInWorldCup2016 = new ArrayList<>();
playersInWorldCup2016.add(teamIndia);
playersInWorldCup2016.add(teamAustralia);
playersInWorldCup2016.add(teamEngland);
playersInWorldCup2016.add(teamNewZeland);
playersInWorldCup2016.add(teamSouthAfrica);
playersInWorldCup2016.add(teamWestIndies);
playersInWorldCup2016.add(teamSriLanka);
playersInWorldCup2016.add(teamPakistan);
System.out.println(playersInWorldCup2016);
// 这时控制台打印是这样的
[[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 = playersInWorldCup2016.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...
    
    @Override
    public 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((Comparator.comparingInt(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((Comparator.comparingInt(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表示,在判断的条件里,匹配任意一个元素返回true
boolean b = list.stream().anyMatch(s -> "cat".equals(s));
System.out.println(b);
// 结果是: true

// allMatch表示,判断的条件里,所有的元素都是,返回true
// 结果是: false
boolean b = list.stream().allMatch(s -> "cat".equals(s));

// noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true
// 结果是:false
boolean 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(Comparator.comparing(Person::getAge)).get();
System.out.println(person);
// 结果是:Person{id=2, name='xiaohong', sex='woman', age=27}

// 求最小值
Person person = list.stream().min(Comparator.comparing(Person::getAge)).get();
// 结果是:Person{id=1, name='xiaoming', sex='man', age=23}
// 当然,还可以求平均值,求和,不过得借助中间操作mapToInt

// 求最大值
// 结果是27
int asInt = list.stream().mapToInt(Person::getAge).max().getAsInt();

// 求最小值
// 结果是23
int asInt = list.stream().mapToInt(Person::getAge).min().getAsInt();

// 求平均值
// 结果是24.6
double asInt = list.stream().mapToInt(Person::getAge).average().getAsDouble();

// 求和
// 结果是123
int sum = list.stream().mapToInt(Person::getAge).sum();
  • reduce方法用到得较少,这里不做过多研究,请参考:https://www.jianshu.com/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<T>

把流中的数据收集到一个List中

toSet()

Set<T>

把流中的数据收集到一个Set中,并去重

toCollection()

Collection<T>

把流中的数据收集到原始类型集合中,可以指定具体集合类型

toMap()

Collector<T, ?, Map<K,U>>

收集为Map类型

toConcurrentMap()

Collector<T, ?, ConcurrentMap<K,U>>

收集为ConcurrentMap类型

counting

Long

计算流中元素的个数

summingInt()

Integer

对流中的元素整数属性求和

averagingInt()

Double

对流中元素的Integer类型求平均值,返回Double类型

summarizingInt()

IntSummary…

对流中元素Integer类型进行求最大,最小,平均值,是个综合求值操作

joining()

String

连接流中元素,并产生成一个字符串,可指定分隔符

maxBy

Optional<T>

选择流中最大元素,按照比较器指定的排序字段

minBy

Optional<T>

选择流中最小元素,按照比较器指定的排序字段

reducing

T

规约操作,指定初始值,然后规约某个字段为一个值

collectingAndThen

T

收集之后,对其结果进行操作

groupingBy

Map<K,List<T>>

将流中的元素进行分组

partitioningBy

Map<Boolean,List<T>>

将流中的元素进行分区

// toList()
 // 在上面的例子中已经出现了不少次,没错他就是将结果收集为List类型的集合
 // 这个方法的底层源码默认采用的集合类型是ArrayList
 List<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(Comparator.comparing(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}}