可以将流看做流水线,这个流水线是处理数据的流水线,一个产品经过流水线会有一道道的工序就如同对数据的中间操作,比如过滤我不需要的,给数据排序能,最后的终止操作就是产品从流水线下来,我们就可以统一打包放入仓库了。
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
一、Stream的特性
Stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
Stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
Stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
二、Stream的创建
(1)Stream可以通过集合数组创建。
1、通过 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();
(2)使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
(3)使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
三、Stream的终止操作
为了方便我们后续的使用,我们先初始化一部分数据:
public class Person {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; //性别
private String area; // 地区
public Person() {
}
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 class LambdaTest {
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,"女","南京"));
}
}
3.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> first = simpleList.stream().findFirst();
// 随便找一个,可以看到findAny()操作,返回的元素是不确定的,
// 对于同一个列表多次调用findAny()有可能会返回不同的值。
// 使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,
// 如果是并行的情况,那就不能确保是第一个。
Optional<Integer> any = simpleList.parallelStream().findAny();
System.out.println("first = " + first.get());
System.out.println("any = " + any.get());
}
@Test
public void matchTest(){
// 判断有没有任意一个人年龄大于35岁
boolean flag = personList.stream().anyMatch(item -> item.getAge() > 35);
System.out.println("flag = " + flag);
// 判断是不是所有人年龄都大于35岁
flag = personList.stream().allMatch(item -> item.getAge() > 35);
System.out.println("flag = " + flag);
}
3.2、归集(toList/toSet/toMap)
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList
、toSet
和toMap
比较常用。
下面用一个案例演示toList
、toSet
和toMap
:
@Test
public void collectTest(){
// 判断有没有任意一个人年龄大于35岁
List<Integer> collect = simpleList.stream().collect(Collectors.toList());
System.out.println(collect);
Set<Integer> collectSet = simpleList.stream().collect(Collectors.toSet());
System.out.println(collectSet);
Map<Integer,Integer> collectMap = simpleList.stream().collect(Collectors.toMap(item->item,item->item+1));
System.out.println(collectMap);
}
3.3、 统计(count/averaging/sum/max/min)
@Test
public void countTest(){
// 判断有没有任意一个人年龄大于35岁
long count = new Random().ints().limit(50).count();
System.out.println("count = " + count);
OptionalDouble average = new Random().ints().limit(50).average();
average.ifPresent(System.out::println);
int sum = new Random().ints().limit(50).sum();
System.out.println(sum);
}
案例:获取员工工资最高的人
Optional<Person> max = personList.stream().max((p1, p2) -> p1.getSalary() - p2.getSalary());
max.ifPresent(item -> System.out.println(item.getSalary()));
里边的比较器可以改为:Comparator.comparingInt(Person::getSalary)
3.4、归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
案例:求
Integer
集合的元素之乘积。
@Test
public void reduceTest(){
Integer result = simpleList.stream().reduce(1,(n1, n2) -> n1*n2);
System.out.println(result);
}
3.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);
}
}
3.6、分组(partitioningBy/groupingBy)
- 分区:将
stream
按条件分为两个Map
,比如员工按薪资是否高于8000分为两部分。 - 分组:将集合分为多个Map,比如员工按性别分组。
案例:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组
@Test
public void groupingByTest(){
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);
}
四、Stream中间操作
4.1、筛选(filter)
该操作符需要传入一个function函数
筛选出
simpleList
集合中大于17的元素,并打印出来
simpleList.stream().filter(item -> item > 17).forEach(System.out::println);
筛选员工中工资高于8000的人,并形成新的集合。
List<Person> collect = personList.stream().filter(item -> item.getSalary() > 8000).collect(Collectors.toList());
System.out.println("collect = " + collect);
4.2、映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map
和flatMap
:
map
:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。flatMap
:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
案例:将员工的薪资全部增加1000。
personList.stream().map(item -> {
item.setSalary(item.getSalary()+1000);
return item;
}).forEach(System.out::println);
将simpleList转化为字符串list
List<String> collect = simpleList.stream().map(num -> Integer.toString(num))
.collect(Collectors.toList());
4.3、排序(sorted)
sorted,中间操作。有两种排序:
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):Comparator排序器自定义排序
案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序
@Test
public void sortTest(){
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
.collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
if (p1.getSalary() == p2.getSalary()) {
return p2.getAge() - p1.getAge();
} else {
return p2.getSalary() - p1.getSalary();
}
}).map(Person::getName).collect(Collectors.toList());
System.out.println("按工资升序排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄升序排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}
4.4、peek操作
peek的调试作用
@Test
public void peekTest(){
// 在stream中间进行调试,因为stream不支持debug
List<Person> collect = personList.stream().filter(p -> p.getSalary() > 5000)
.peek(System.out::println).collect(Collectors.toList());
// 修改元素的信息,给每个员工涨工资一千
personList.stream().peek(p -> p.setSalary(p.getSalary() + 1000))
.forEach(System.out::println);
}
4.5、其他操作
流也可以进行合并、去重、限制、跳过等操作。
@Test
public void otherTest(){
// distinct去掉重复数据
// skip跳过几个数据
// limit限制使用几个数据
simpleList.stream().distinct().skip(2).limit(3).forEach(System.out::println);
}
// 11,11,22,22,11,23,43,55,78
// 去重 11,22,23,43,55,78
// 掉过两个 23,43,55,78
// 使用3个 23,43,55
4.5、其他操作
流也可以进行合并、去重、限制、跳过等操作。
@Test
public void otherTest(){
// distinct去掉重复数据
// skip跳过几个数据
// limit限制使用几个数据
simpleList.stream().distinct().skip(2).limit(3).forEach(System.out::println);
}
// 11,11,22,22,11,23,43,55,78
// 去重 11,22,23,43,55,78
// 掉过两个 23,43,55,78
// 使用3个 23,43,55