一、什么是Stream流
Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作,使用Stream API对集合数据进行操作就类似使用SQL执行数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
集合讲的是数据,流讲的是计算
注意:
- Stream自己不会存储元素
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才会去执行
二、Stream操作的三个步骤
1、创建Stream
一个数据源,获取一个流
package com.xuzhi.stream;
/**
* Author: 徐志
* Date: 2020/8/1 15:20
*/
import com.xuzhi.entity.Employee;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream操作的三个步骤
* 1.创建Stream
* 2.中间操作
* 3.终止操作
*/
public class TestStreamAPI1 {
//1.创建Stream的几种方式
@Test
public void test1(){
//1.可以通过Collection系列集合提供的stream()或者parallelStream()
List<String> list=new ArrayList<>();
Stream<String> stream1=list.stream();
//2.通过Arrays中的静态方法获取数组流
Employee[] emps=new Employee[10];
Stream<Employee> stream2= Arrays.stream(emps);
//3.通过Stream中的静态方法of()创建
Stream<String> stream3 = Stream.of("aa", "dd", "dd", "ddddd");
//4.创建无限流
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10)
.forEach(System.out::println);
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
}
2、中间操作
一个中间操作链,对数据源的数据进行处理
注意:多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”
package com.xuzhi.stream;
/**
* Author: 徐志
* Date: 2020/8/1 15:33
*/
import com.xuzhi.entity.Employee;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
/**
* 中间操作
* 筛选和切片
* filter--接收Lambda,从流中排出某些元素
* limit--截断流,使其元素不超过给定 的数量
* skip(n)--跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不足n个,则返回一个空流
* distinct---筛选,通过流所产生的hashCode和equals()去除重复元素
*/
public class TestStreamAPI2 {
List<Employee> employees= Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.99),
new Employee("王五",50,6666.99),
new Employee("赵六",16,3333.99),
new Employee("田七",8,7777.99)
);
//内部迭代
@Test
public void test1(){
employees.stream()
.filter((e)->e.getAge()>35)
.forEach(System.out::println);
}
//外部迭代
@Test
public void test2(){
Iterator<Employee> it=employees.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
@Test
public void test3(){
employees.stream()
.filter((e)->e.getSalary()>5000)
.limit(2)
.forEach(System.out::println);
}
@Test
public void test4(){
employees.stream()
.filter((e)->e.getSalary()>5000)
.skip(2)
.forEach(System.out::println);
}
/**
* 映射
* map--接收lambda,将元素转换成其他形式获取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
* flatMap--接收一个函数作为参数,将流中的每一个值换成另一个流,然后把所有的流连成一个流
*
*/
@Test
public void test5(){
List<String> list=Arrays.asList("aaa","bbb","ccccc","ddd","ee");
list.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
/**--------------------------------------------**/
Stream<Stream<Character>> stream=list.stream()
.map(TestStreamAPI2::filterCharacter);
stream.forEach((sm)->{
sm.forEach(System.out::println);
});
/**--------------------------------------------**/
list.stream()
.flatMap(TestStreamAPI2::filterCharacter)
.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str){
List<Character> list=new ArrayList<>();
for (Character c:str.toCharArray()) {
list.add(c);
}
return list.stream();
}
/**
* 排序
* sorted()--自然排序
* sorted(Comparator com)
*
*/
@Test
public void test7(){
List<String> list=Arrays.asList("aa","ddd","dddf","rfgg");
list.stream()
.sorted()
.forEach(System.out::println);
employees.stream()
.sorted((e1,e2)->{
if(e1.getAge()==e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else{
return Integer.compare(e1.getAge(),e2.getAge());
}
})
.forEach(System.out::println);
}
}
map和flatMap的区别
在讲解mapflatMap的区别之前,先来看看集合中add()和addALL()方法的区别
@Test
public void test(){
List list1=new ArrayList();
List list2=new ArrayList();
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list1.add(1);
list1.add(2);
list1.add(list);
System.out.println(list1.toString());
list2.add(1);
list2.add(2);
list2.addAll(list);
System.out.println(list2.toString());
}
运行结果
使用add()方法,他会将集合当做元素插入进集合当中,而addAll()而是将此集合中的元素插入。
map和flatMap与之类似,遇到同样的情况,map会返回一个Stream的集合,集合里面又有一个Stream的集合,二flatMap不会
3、终止操作
一个终止的操作,执行中间操作链,并产生结果
package com.xuzhi.stream;
/**
* Author: 徐志
* Date: 2020/8/2 8:11
*/
import com.xuzhi.entity.Employee;
import org.junit.Test;
import java.util.*;
import java.util.stream.Collectors;
/**
* 终止操作
*/
public class TestStreamAPI3 {
List<Employee> employees= Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.99),
new Employee("王五",50,6666.99),
new Employee("赵六",16,3333.99),
new Employee("田七",18,7777.99)
);
/*
查找与匹配
allMatch--检查是否匹配所有的元素
anyMatch--检查是否至少匹配一个元素
noneMatch--检查是否没有匹配所有元素
findFirst--返回第一个元素
findAny--返回当前流中的任意元素
count--返回流中元素的总个数
max--返回流中的最大值
min--返回流中的最小值
*/
@Test
public void test1(){
boolean b1 = employees.stream()
.allMatch((e) -> e.getAge() == 18);
System.out.println(b1);
boolean b2 = employees.stream()
.anyMatch((e) -> e.getAge() == 18);
System.out.println(b2);
boolean b3 = employees.stream()
.noneMatch((e) -> e.getAge() == 18);
System.out.println(b3);
Optional<Employee> first = employees.stream()
.sorted((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()))
.findFirst();
System.out.println(first.get());
Optional<Employee> any = employees.stream()
.sorted()
.findAny();
System.out.println(any.get());
}
@Test
public void test2(){
long count = employees.stream()
.count();
System.out.println(count);
Optional<Employee> max = employees.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(min.get());
}
/**
* 规约
* reduce--可以将流中的元素反复结合起来,得到一个值
*/
@Test
public void test3(){
List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9,0);
Integer reduce = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(reduce);
Optional<Double> reduce1 = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
}
/**
* 收集
*
* collect--将流转换成其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
*/
@Test
public void test4(){
List<String> collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
Set<Double> collect1 = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.toSet());
collect1.forEach(System.out::println);
HashSet<String> collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
collect2.forEach(System.out::println);
}
@Test
public void test5(){
Long collect = employees.stream()
.collect(Collectors.counting());
System.out.println(collect);
Double collect1 = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(collect1);
Double collect2 = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(collect2);
Optional<Employee> collect3 = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(collect3.get());
}
//分组
@Test
public void test6(){
Map<Integer, List<Employee>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getAge));
System.out.println(collect);
}
//分区
@Test
public void test8(){
Map<Boolean, List<Employee>> collect = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 6000));
System.out.println(collect);
}
@Test
public void test9(){
DoubleSummaryStatistics collect = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(collect.getMax());
}
//连接
@Test
public void test10(){
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println(collect);
}
}
三、小练习
package com.xuzhi.stream;
import com.xuzhi.entity.Employee;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* Author: 徐志
* Date: 2020/8/2 10:33
*/
public class Zuoye {
/**
* 给定一个数字列表,如何返回一个由每个数的平方组成的列表
* 给定【1,2,3,4,5】,应该返回【1,4,9,16,25】
*/
@Test
public void test1(){
Integer[] nums=new Integer[]{1,2,3,4,5};
Arrays.stream(nums)
.map((x)->x*x)
.forEach(System.out::println);
}
/**
* 怎样用map+reduce数出有多少个Employee
*/
List<Employee> employees= Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.99),
new Employee("王五",50,6666.99),
new Employee("赵六",16,3333.99),
new Employee("田七",8,7777.99)
);
public void test2(){
Optional<Integer> reduce = employees.stream()
.map((e) -> 1)
.reduce(Integer::sum);
System.out.println(reduce.get());
}
}
四、并行流和顺序流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
java8中将并行进行了优化,我们可以很容易的对数据进行并行操作,StreamAPI可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换
- ForkJoinCalculate.java
package com.xuzhi.forkJoin;
import java.util.concurrent.RecursiveTask;
/**
* Author: 徐志
* Date: 2020/8/2 11:20
*/
/**
使用ForkJoin框架必须继承RecursiveAction或者RecursiveTask。两者的区别是RecursiveTask有返回值,RecursiveAction没有返回值
*/
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID=233224555L;
private long start;
private long end;
private static final long THREADHOLD=10000;
public ForkJoinCalculate(long start,long end){
this.start=start;
this.end=end;
}
@Override
protected Long compute() {
long length=end-start;
if(length<=THREADHOLD){
long sum=0;
for (long i=start;i<=end;i++){
sum+=i;
}
return sum;
}else{
long mid=(start+end)/2;
ForkJoinCalculate left=new ForkJoinCalculate(start,mid);
left.fork();
ForkJoinCalculate right=new ForkJoinCalculate(mid+1,end);
right.fork();
return left.join()+right.join();
}
}
}
- TestForkJoin.java
package com.xuzhi.forkJoin;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* Author: 徐志
* Date: 2020/8/2 11:29
*/
public class TestForkJoin {
/*
使用ForkJoin
*/
@Test
public void test1(){
Instant startTime = Instant.now();
ForkJoinPool pool=new ForkJoinPool();
ForkJoinTask<Long> task=new ForkJoinCalculate(0,100000000L);
Long sum=pool.invoke(task);
System.out.println(sum);
Instant endTime = Instant.now();
System.out.println(Duration.between(startTime, endTime).toMillis());
}
/*
传统的for循环
*/
@Test
public void test2(){
Instant startTime = Instant.now();
long sum=0L;
for (long i = 0; i <=100000000L; i++) {
sum+=i;
}
System.out.println(sum);
Instant endTime = Instant.now();
System.out.println(Duration.between(startTime, endTime).toMillis());
}
//java8并行流
public void test3(){
Instant startTime = Instant.now();
LongStream.rangeClosed(0,100000000L)
.parallel()
.reduce(0,Long::sum);
Instant endTime = Instant.now();
System.out.println(Duration.between(startTime, endTime).toMillis());
}
}
使用forkJoin框架或者并行流都是为了充分利用CPU的资源,当数据量过小的时候,for循环会比其他两种方式块,当数据量较大时,并行流的优势就提现出来了
五、Optional
Optional<T>类是一个容器类,代表一个值存在或者不存在,原来使用Null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常
常用方法:
- Optional.of(T t):创建一个Optional实例
- Optional.empty():创建一个空的Optional实例
- Optional.ofNullable(T t):若t不为null,创建Optional实例,否则则创建空实例
- isPresent():判断是否包含值
- orElse(T t):如果调用 的对象包含值,返回该值,否则返回t
- orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
- map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
- flatMap(Function mapper):与map类似,要求返回值必须是Optional
package com.xuzhi.optional;
import com.xuzhi.entity.Employee;
import org.junit.Test;
import java.util.Optional;
/**
* Author: 徐志
* Date: 2020/8/2 14:53
*/
public class TestOptional {
@Test
public void test1(){
//不能为null
Optional<Employee> op=Optional.of(new Employee());
Employee employee = op.get();
System.out.println(employee);
}
@Test
public void test2(){
Optional<Employee> op = Optional.empty();
System.out.println(op.get());
}
@Test
public void test3(){
//有值就获取,没值就什么都不做
Optional<Employee> op = Optional.ofNullable(null);
/* if (op.isPresent()){
System.out.println(op.get());
}*/
Employee employee = op.orElse(new Employee("李四", 66, 99999));
System.out.println(employee);
Employee employee1 = op.orElseGet(() -> new Employee());
System.out.println(employee1);
}
@Test
public void test4(){
Optional<Employee> dd = Optional.ofNullable(new Employee("dd", 33, 4343));
/* Optional<String> s = dd.map((e) -> e.getName());
System.out.println(s.get());*/
Optional<String> s = dd.flatMap((e) -> Optional.of(e.getName()));
System.out.println(s);
}
}
六、总结
本人观看的学习资料为B站尚硅谷教学视频。简单的学习了解了Stream流操作集合的强大,过去对集合中的元素进行操作可能需要写比较复杂的代码,现在通过Stream流配合lambda表达式,实现一些功能实在是太轻松了。附上本人微信公众号二维码,欢迎一起交流学习