知识点:
Stream流的作用
Steam流是什么
Stream流怎么使用,使用Stream的步骤:创建,中间操作,终止操作
创建流的五种方式,中间操作API以及终止操作API
Stream流可能是java8新特性中最成功的、也是用的最爽的一个特性了。首先用简单的介绍一下它的作用
Stream流的作用
有了Stream流,从此集合的操作就可以告别繁琐的for循环了。使用Stream流对集合进行操作,就类似于使用SQL执行数据库的查询,也可以使用Stream API来并行执行操作。简而言之,Stream流提供了一种高效易用的处理集合数据的方式。那接下来就举一个小栗子来说明它的强大之处。
代码示例:
首先需要创建一个员工集合,在博客末尾会贴出Employee类的代码,员工属性分别是:id,姓名,年龄,工资和状态
private List<Employee> emps= Arrays.asList(
new Employee(103,"萧何",44,8888.77, Employee.Status.BUSY),
new Employee(101,"刘邦",55,10000.11,Employee.Status.FREE),
new Employee(102,"张良",45,9999.99,Employee.Status.VOCATION),
new Employee(106,"陈平",38,5555.55,Employee.Status.BUSY),
new Employee(104,"韩信",33,3333.33,Employee.Status.FREE),
new Employee(105,"周勃",29,6666.66,Employee.Status.FREE),
new Employee(105,"周勃",29,6666.66,Employee.Status.FREE)
);
需求1:获取年龄在40岁以上的员工集合
需求2:获取工资在7000元以上的员工
在没有stream流之前的代码可能是这样
//根据年龄过滤
public List<Employee> filterEmployeeAge(int age){
List<Employee> ageEmps=new ArrayList<>();
for(int i=0;i<emps.size();i++){
Employee emp=emps.get(i);
if(emp.getAge()>age){
ageEmps.add(emp);
}
}
return ageEmps;
}
//根据薪水过滤
public List<Employee> filterEmploySalary(double salary){
List<Employee> salaryEmps=new ArrayList<>();
for(int i=0;i<emps.size();i++){
Employee emp=emps.get(i);
if(emp.getSalary()>salary){
salaryEmps.add(emp);
}
}
return salaryEmps;
}
@Test
public void test1(){
List<Employee> ageEmps=filterEmployeeAge(40);
for(Employee emp:ageEmps){
System.out.println(emp);
}
System.out.println("------------------");
List<Employee> salaryEmps=filterEmploySalary(7000);
for(Employee emp:salaryEmps){
System.out.println(emp);
}
}
当然了。上述代码可以使用策略模式进行优化,但其不是本篇的重点,在这里就不进行详细的介绍了。那么如果我们先要根据年龄过滤,再根据薪水过滤,那么就又需要写一个类似的方法,代码中充斥着大量的重复代码。做了很多重复操作,唯一不同的可能就是过滤条件不同了。那接下来使用Stream流又是什么样的呢
使用Stream流的代码示例:
@Test
public void test2(){
//获取年龄大于40岁的员工
emps.stream()
.filter((e)->e.getAge()>40)
.forEach(System.out::println);
System.out.println("------------------");
//获取薪水大于7000的员工
emps.stream()
.filter((e)->e.getSalary()>7000)
.forEach(System.out::println);
}
其运行结果是一致的,如下:
上述案例对比可以看出。Stream流大大的简化了对集合的操作。
什么是Stream流?
简单的讲:Stream流就是一个数据渠道,用于操作数据源(集合、数组)所产生的元素序列。
注意点:
- Stream自己不会存储元素
- Stream不会改变数据源对象,返回一个持有结果的新Stream
- Stream 操作是延迟执行,这意味着它们会等到需要的结果才执行
如何使用Stream流呢?
使用Stream流需要三个步骤:
- 创建Stream流:根据数据源(集合、数组),获取一个Stream流
- 中间操作:对数据源进行一系列处理
- 终止操作:执行中间操作,并产生结果
一)、创建Stream流的方式
1、java8对Collection接口进行了扩张,提供了两个获取流的方法
default Stream<E> stream():返回一个顺序流
default Stream<E> parallelStream():返回一个并行流
代码示例:使用员工集合emps创建集合
Stream<Employee> stream = emps.stream(); //创建一个顺序流
Stream<Employee> parallelStream = emps.parallelStream(); //创建一个并行流
2、java8中Arrays类中提供了一个静态的方法,使用数组创建流
public static <T> Stream<T> stream(T[] array):返回一个流
重载形式, 能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
Integer[] nums={2,3,4,5};
Stream<Integer> arrStream = Arrays.stream(nums);//使用数组创建一个流
3、使用Stream提供的静态方法of(),显示的创建一个流。它可以接收任意参数
public static<T> Stream<T> of(T... values)
Stream<String> ofStream = Stream.of("hello", "java", "8", "新特性");
4、创建无限流,可以使用Stream.iterate()方法和Stream.generate()方法,创建无限流
迭代:Public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成:public static<T> Stream<T> generate(Supplier<T> s)
Stream<Integer> iterateStream = Stream.iterate(0, (x) -> x + 2);
iterateStream.limit(10).forEach(System.out::println);
Stream<Double> generateStream = Stream.generate(Math::random);
generateStream.limit(2).forEach(System.out::println);
二)、中间操作
多个中间操作可以连接形成一条流水线,除非流水线触发终止操作,否则中间操作不会执行任何处理,而在终止操作时会对中间操作全部处理,这被称之为“惰性求值”。
举个小栗子简单说明:还是对员工集合进行操作,没有调用终止操作
@Test
public void test4(){
emps.stream().filter((e)->{
System.out.println("只有中间操作,无终止操作");
return e.getAge()>35;
}); //filter()方法时一个中间操作方法
}
执行结果:
有调用终止操作的示例:
@Test
public void test5(){
emps.stream().filter((e)->{//filter()方法时一个中间操作方法
System.out.println("短路");
return e.getAge()>35;
}).limit(2).forEach(System.out::println); //limit()方法为中间操作方法
}
执行结果:
中间操作API汇总:
一)、筛选与切片
filter() - 接收一个lambda,从流程中排除某些元素
limit(n) - 截断了,使元素不得超过给定数量
skip(n) - 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
distinct - 筛选,通过流所生成的元素的hashCode()和equals()去除重复元素
二)映射:
map -- 接收lambda,将元素转换成其他形式,或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap -- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
三)排序
sorted() -- 自然排序
Sorted(Comparator com) --定制排序
中间操作API详解
一)、筛选与切片
还是使用员工集合对下方法进行说明介绍,结果就不进行粘贴了。
filter() - 接收一个lambda,从流程中排除某些元素
代码示例:
public void test6(){
emps.stream()
.filter((e)->e.getAge()>35) //过滤员工年龄大于35岁的员工
.forEach(System.out::println);
}
limit(n) - 截断流,使元素不得超过给定数量
代码示例:
@Test
public void test7(){
emps.stream()
.filter((e)->e.getAge()>35) //过滤员工年龄大于35岁的员工
.limit(2) //且只获取前2条数据
.forEach(System.out::println);
}
skip(n) - 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
代码示例:
@Test
public void test8(){
emps.stream()
.filter((e)->e.getAge()>35) //过滤员工年龄大于35岁的员工
.skip(2) //跳过前2条数据,获取之后的数据
.forEach(System.out::println);
}
distinct() - 筛选,通过流所生成的元素的hashCode()和equals()去除重复元素
代码实列:
@Test
public void test9(){
emps.stream()
.filter((e)->e.getAge()<35) //过滤员工年龄大于35岁的员工
.distinct() //如果流中有重复元素,则去除重复元素
.forEach(System.out::println);
}
二)映射:
map() -- 接收lambda,将元素转换成其他形式,或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
代码示例一:获取所有员工的姓名,并添加到集合中返回
@Test
public void test10(){
Stream<String> namesStream=emps.stream()
.map((e)->e.getName()); //提取所有员工的姓名,生成一个新的存储字符串的流
List<String> names = namesStream.collect(Collectors.toList());//这是调用一个终结操作,作用就是把流转化为集合
System.out.println(names);
}
输出结果:
代码示例二:把字符串数组中的小写字符串转换为大写
@Test
public void test11(){
String[] strs={"aaa","bbb","ccc","ddd"};
Stream<String> strStream= Arrays.stream(strs)
.map(String::toUpperCase); //把流中所有的字符串转换为大写,
String[] strs1= strStream.collect(Collectors.toList()).toArray(new String[0]);
for(int i=0;i<strs.length;i++){
System.out.print(strs1[i]+" ");
}
}
输出结果:
flatMap() -- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
flatMap和map()的区别,有点像集合中的add()方法和addAll()方法
三)排序
sorted() -- 自然排序
sorted(Comparator com) --定制排序
@Test
public void test12(){
//自然排序
List<String> strsList=Arrays.asList("aaa","ccc","bbb");
strsList.stream()
.sorted().forEach(System.out::println);
System.out.println("------------");
//定制排序,员工如果年龄不同,按照年龄排序,年龄相同,按照姓名排序
emps.stream()
.sorted((x, y) -> {
if(x.getAge()==y.getAge()){
return x.getName().compareTo(y.getName());
}else {
return Integer.compare(x.getAge(),y.getAge());
}
}).forEach(System.out::println);
}
输出结果:
3)、终止操作
执行中间操作,产生结果
终止操作API汇总
一)、查找与匹配:
allMatch -- 检查是否匹配所有元素,
anyMatch -- 检查至少匹配一个元素
noneMatch -- 检查是否没有匹配所有元素
findFirst -- 返回第一个元素
findAny -- 返回当前流中的任意元素
count -- 返回流中的总个数
max -- 返回流中的最大值
min -- 返回最小值
二)、归约
reduce(T identity,BinaryOperator ) /reduce(BinaryOperator): 可以将流中的元素反复结合起来,得到一个值
三)、收集
collect(Collector c) - 将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中的元素做汇总操作
Collector接口中的方法的实现决定了如何对流执行收集操作(如收集List、set、Map),但是Collectors使用类提供了很多静态的方法,可以方便的创建常见收集器实例,
终止操作API详解:
allMatch -- 检查是否匹配所有元素,
anyMatch -- 检查至少匹配一个元素
noneMatch -- 检查是否没有匹配所有元素
代码示例:
@Test
public void test13(){
//所有员工处于忙碌状态,返回true,不是,返回false
boolean allBusy= emps.stream()
.allMatch((e)->e.getStatus().equals(Employee.Status.BUSY));
System.out.println("所有员工都处于忙碌状态吗?"+allBusy);
//至少有一个员工处于忙碌状态,返回true,不是,返回false
boolean anyBusy= emps.stream()
.anyMatch((e)->e.getStatus().equals(Employee.Status.BUSY));
System.out.println("存在至少一个员工处于忙碌状态吗?"+anyBusy);
//没有员工处于忙碌状态,返回true,否则返回false
boolean noneBusy= emps.stream()
.noneMatch((e)->e.getStatus().equals(Employee.Status.BUSY));
System.out.println("没有员工处于忙碌状态吗?"+anyBusy);
}
执行结果:
findFirst -- 返回第一个元素
findAny -- 返回当前流中的任意元素
@Test
public void test14(){
//获取薪水最低的员工
Optional<Employee> opEmp= emps.stream()
.sorted((x,y)->Double.compare(x.getSalary(),y.getSalary())) //根据薪水排序
.findFirst(); //获取排在第一个位置的员工
System.out.println(opEmp.get());
//随机获取一个处于忙碌状态的员工
Optional<Employee> busyEmp=emps.stream()
.filter((e)->e.getStatus().equals(Employee.Status.BUSY)) //根据状态进行过滤
.findAny(); //从忙碌状态的员工中随机获取一个
System.out.println(busyEmp.get());
}
执行结果:
Optional容器类,它提供了另外一种解决空指针异常的方案,可能会在之后的博客中详解
count -- 返回流中的总个数
max -- 返回流中的最大值
min -- 返回最小值
@Test
public void test15(){
//处于空闲状态的员工总数
long count= emps.stream()
.filter((e)->e.getStatus().equals(Employee.Status.FREE))
.count();
System.out.println("处于空闲状态的员工总数:"+count);
//获取最高的工资
Optional<Double> opSalary= emps.stream()
.map(Employee::getSalary)
.max(Double::compare);
System.out.println("最高的工资是:"+opSalary.get());
//获取工资最低的员工
Optional<Employee> opEmp =emps.stream()
.min((x,y)->Double.compare(x.getSalary(),y.getSalary()));
System.out.println("工资最低的员工:"+opEmp.get());
}
运行结果:
二)、归约
reduce(T identity,BinaryOperator ) / reduce(BinaryOperator): 可以将流中的元素反复结合起来,得到一个值
@Test
public void test17(){
List<Integer> intList=Arrays.asList(1,2,3,44,5);
Integer sum =intList.stream().reduce(0,(x,y)->x+y);
System.out.println("求和:"+sum);
System.out.println("---------");
Optional<Double> sumSalary=emps.stream()
.map((x)->x.getSalary())
.reduce(Double::sum);
System.out.println("所有员工的工资总和"+sumSalary.get());
}
运行结果:
三)、收集
collect(Collector c) - 将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中的元素做汇总操作
Collector接口中的方法的实现决定了如何对流执行收集操作(如收集List、set、Map),但是Collectors使用类提供了很多静态的方法,可以方便的创建常见收集器实例,
代码实列:
@Test
public void test18(){
//获取流中的姓名,并转换为姓名的集合
List<String> names=emps.stream()
.map((e)->e.getName())
.collect(Collectors.toList());
System.out.println(names);
}
运行结果:
之后的博客会对Collectors这个类进行详细的介绍。这里只对流中的姓名进行收集,并转换为集合的简单实列
总结:
熟练掌握这些API,还是需要反复迭代。并加以理解
参考:张长志老师的视频