Java8中的Stream是对容器对象功能的增强,他专注于对容器对象进行各种非常便利、高效的操作,Stream Api 借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性,同时,他提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。通常,编写并行代码很难并容易出错,但使用Stream API 无需编写一行多线程的代码,就可以很方便的写出高性能的并发程序。
我觉得我们可以将流看做流水线,这个流水线是处理数据的流水线,一个产品经过流水线会有一道道的工序就如同对数据的中间操作,比如过滤我不需要的,给数据排序,最后的终止操作就是产品从流水线下来,我们就可以统一打包放入仓库了
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源—》数据转转–》执行数据操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以向链条样排列,变成一个管道,如下图所示:
Stream有几个特性:
- stream不存储数据,而是按照特定的规则对数据进行计算
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行
1.Stream流的创建
(1)Stream可以通过集合数组创建
- 通过java.util.Collection.stream() 方法用集合创建流,我们发现
default Stream<E> stream(){
return StreamSupport.stream(spliterator,false);
}
List<String> list = Arrays.asList("a","b","c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
// 合并两个流
Stream<String> concat = Stream.concat(list, stream);
(2)使用java.util.Arrays.stream(T[ ] array) 方法用数组创建流
int [] array = {1,3,5,6,8};
IntStream stream = Arrays.stream(array);
(3)使用Stream的静态方法: of()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
2.Stream的终止操作
为了方便我们后续使用,我们先初始化一部分数据
public class Person {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; //性别
private String area; // 地区
public Person(String name, int salary, int age, String sex, String area) {
this.name = name;
this.salary = salary;
this.age = age;
this.sex = sex;
this.area = area;
}
public Person(){
}
getting、setting和toString()省略。。
初始化数据,我们设计一个简单的集合和一个复杂的集合
public class lamdaTest {
List<Person> personList = new ArrayList<Person>();
List<Integer> simpleList = Arrays.asList(15,22,9,11,33,52,14);
@Before
public void initData(){
personList.add(new Person("张三",3000,23,"男","太原"));
personList.add(new Person("李四",7000,34,"男","西安"));
personList.add(new Person("王五",5200,22,"女","太原"));
personList.add(new Person("小黑",1500,33,"女","上海"));
personList.add(new Person("狗子",8000,44,"女","北京"));
personList.add(new Person("铁蛋",6200,36,"女","南京"));
}
(1)遍历/匹配(foreach/find/match)
@Test
public void foreachTest(){
// 遍历打印打印集合元素
simpleList.stream().forEach(System.out::println);
// 其实可以简化操作
simpleList.forEach(System.out::println);
}
// 找寻元素
@Test
public void findTest(){
// 拿到任意一个元素
Optional<Integer> any = simpleList.stream().findAny();
// 拿到第一个元素
Optional<Integer> first = simpleList.stream().findFirst();
// 如果存在该元素,则打印
any.ifPresent(System.out::println);
/**
* 在单线程环境下,any和first拿到的其实是同一个数据,使用findAny()是为了更高效的性能。
* 如果是数据较少,串行地 情况下,一般会返回第一个结果,但是在多线程的环境下
* stream会把流进行截断分成几部分,那摩每个线程拿到的数据就可能不一样了,
*/
// 多线程环境下
Optional<Integer> firstMore = simpleList.parallelStream().findFirst();
firstMore.ifPresent(System.out::println);
Optional<Integer> anyMore = simpleList.parallelStream().findAny();
anyMore.ifPresent(System.out::println);
}
// 匹配元素
@Test
public void MatchTest(){
// 是不是所有人的工资都大于8k
boolean b = personList.stream().allMatch(p -> p.getSalary() > 5000);
System.out.println(b);
// 是不是有人的工资大于5000
boolean flag = personList.stream().anyMatch(person -> person.getSalary() > 5000);
System.out.println(flag);
}
(3) 统计(count/averaging/sum/max/min)
// 统计(count/averaging/sum/max/min)
@Test
public void test(){
// 统计集合中有多少个对象
long count = personList.stream().count();
System.out.println(count);
// 平均年龄
OptionalDouble average = simpleList.stream().mapToInt(i -> i).average();
average.ifPresent(System.out::println);
// 求和
int sum = IntStream.of(1, 2, 3, 4, 5).sum();
System.out.println(sum);
// 求最大值 new Random().ints() 会生成一个包含20个数的Stream流
OptionalInt max = new Random().ints(20).max();
// max.ifPresent(System.out::println);
// max.ifPresent(new IntConsumer() {
// @Override
// public void accept(int value) {
// System.out.println(value);
// }
// });
max.ifPresent(value -> System.out.println(value));
// 求最小值
OptionalInt min = new Random().ints(20).min();
min.ifPresent(System.out::println);
// 求工资最高的员工
Optional<Person> max1 = personList.stream().max((o1, o2) -> o1.getSalary() - o2.getSalary());
max1.ifPresent(person -> System.out.println("工资最高的员工是:"+ person));
}
(3) 归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值的操作
// 规约
@Test
public void reduceTest(){
// 1代表一开始的值是多少 n1代表结果,n2代表当前遍历到的哪个值,在一次遍历之后将n1*n2的值赋值给n1
int reduce = IntStream.of(2, 1, 3, 4).reduce(1, (n1, n2) -> n1 * n2);
System.out.println(reduce);
int reduce1 = IntStream.of(2, 1, 3, 4).reduce(0, (n1, n2) -> n1 + n2);
System.out.println(reduce1);
}
(5)接合(joining)
//joining可以将Stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
@Test public void joiningTest(){
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
System.out.println("拼接后的字符串:" + string);
}
(6)分组(partitioningBy/groupingBy)
- 分区: 将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分
- 分组:将集合分为多个Map,比如员工按照性别分组
// 分区/分组
@Test
public void groupingByTest(){
// 将工资大于5k的分true组,不满足的分到false组,这就是分区
Map<Boolean, List<Person>> collect = personList.stream().collect(Collectors.partitioningBy(person -> person.getSalary() > 5000));
System.out.println(collect);
// 分组,根据性别分组
Map<String, List<Person>> collect1 = personList.stream().collect(Collectors.groupingBy(person -> person.getSex()));
System.out.println(collect1.get("男"));
}
(6) 归集(toList/toSet/toMap)
因为流不存储数据,那摩在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里,toList、toSet和toMap比较常用
//归集(toList/toSet/toMap)
@Test
public void collectTest(){
List<Integer> collect = simpleList.stream().collect(Collectors.toList());
Set<Integer> collect1 = simpleList.stream().collect(Collectors.toSet());
// 构造出k-名字,v-person对象的Map集合
personList.stream().collect(Collectors.toMap(person -> person.getName(),person -> person));
}
3.Stream中间操作
(1)筛选(filter)
该操作符需要传入一个function函数
// 筛选(filter)/ 过滤
@Test
public void filterTest(){
// 将工资大于5000的人过滤出来放到List集合中
List<Person> collect = personList.stream().filter(person -> person.getSalary() > 5000).collect(Collectors.toList());
System.out.println(collect);
}
(2) 映射(map)
映射,可以将一个流的元素按照一定的映射规则映射到另外一个流中,分为map和flatMap
- map: 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
- flatMap: 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有的流连接成一个流
// 映射map
@Test
public void mapTest(){
// 将所有员工的工资印射成一个List集合,集合里面存放的是所有员工的薪资
List<Integer> collect = personList.stream().map(person -> person.getSalary()).collect(Collectors.toList());
List<Person> collect1 = personList.stream().map(p ->{
p.setSalary(p.getSalary()+1000);
return p;
}).collect(Collectors.toList());
}
(3)排序(sorted)
sorted,中间操作,有两种排序:
- sorted(): 自然排序,流中元素需实现Comparable接口
- sorted(Comparator com): Comparator 排序器自定义排序
// 排序
@Test
public void sortTest(){
// 将员工的工资按照从低到高排序
personList.stream().sorted((p1,p2)-> p1.getSalary() - p2.getSalary()).forEach(System.out::println);
}
//(4)peek操作
//(4)peek操作
@Test
public void peekTest(){
// 修改元素的信息,给每个员工涨工资1k
List<Person> collect = personList.stream().peek(person -> person.setSalary(person.getSalary() + 1000)).collect(Collectors.toList());
}
(5) 其他操作
流也可以进行合并、去重、限制、跳过等操作
@Test
public void otherTest(){
// limit 找出工资前三名的人
personList.stream().sorted(((o1, o2) -> o2.getSalary() - o1.getSalary())).limit(3).forEach(System.out::println);
// 去掉集合中前两个元素之后进行剩下的元素去重操作
IntStream.of(1,2,3,3,5,5,6,6,7,8).distinct().skip(2).forEach(System.out::println);
}