Java8 Stream流
Stream流特性
Stream自己不会存数据,而且它也不能改变源对象,而是返回一个新的Stream。
流与集合是不同的,因为流不存储值,流的目的是处理值...
Stream流使用过程
中间操作的都会返回一个新的流对象,故可以多个中间操作叠加(链式操作);终止操作用于返回我们最终需要的数据,且只能有一个终止操作。
1、创建Stream
从一个数据源(数组,集合)获取流。
2、中间操作
一个中间操作链,对数据源的数据进行处理。
3、终止操作
一旦终止了,这个Stream就到此为止了,不可以再使用了
Stream流终止操作
终止操作:一次性处理全部Stream是有延迟操作的,只有执行了终止操作(Terminal),才会去执行中间链。
延迟计算:
创建流并不会导致数据流动,当终止操作需要值时,流才会从集合中获取值将其提供,最后,当所有集合值都被流提供后,流就被耗尽了,并且无法再次使用。
创建Stream流
1、集合和数组
//1. 通过集合提供 stream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2. 通过 Arrays 提供 stream() 方法
//stream()是Arrays类的泛型静态方法,泛型类型由传入的数组类型确定 public static <T> Stream<T> stream(T[] array)
Integer[] nums = new Integer[10];
Stream<Integer> stream2 = Arrays.stream(nums);
2、Stream.of()
泛型静态方法,泛型类型由传入的类型确定
public static<T> Stream<T> of(T... values)
Stream.of(T... values)⽅法是Stream接⼝的⼀个静态⽅法,其底层调⽤的是 Arrays.stream(T[] array)⽅法
//3. 通过 Stream 提供 of() 方法
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);
//这里T是什么呢 public static<T> Stream<T> of(T... values)
Stream<? extends Serializable> stream = Stream.of(1, 2, "1", 4, 5);
3、迭代和生成
public void test1(){
//4. 创建无限流2种方式,迭代和生成
//①迭代
Stream<Integer> stream4 = Stream.iterate(0, (s) -> s + 2);
stream4.limit(3).forEach(System.out::println);
//②生成
Stream<Double> stream5 = Stream.generate(Math::random);
stream5.limit(3).forEach(System.out::println);
}
Stream流中间操作
准备的数据
实体类:用lombok省略了set/get...
@NoArgsConstructor @AllArgsConstructor @Data public class Employee { private String name; private Integer age; private Double salary; }
测试数据准备:
List<Employee> employees = Arrays.asList( new Employee("张三", 18, 9999.99), new Employee("李四", 20, 3333.33), new Employee("王五", 35, 7777.77), new Employee("赵六", 8, 6666.66), new Employee("田七", 19, 5555.55) );
1、过滤、去重
filter(Predicate<T>)——有选择地处理流元素 distinct——去重,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 20, 3333.33),
new Employee("王五", 35, 7777.77),
new Employee("赵六", 8, 6666.66),
new Employee("田七", 19, 5555.55)
);
Stream<Employee> stream=employees.stream();
//Stream<T> filter(Predicate<? super T> predicate);
stream.filter(e->e.getSalary()>7000).forEach(System.out::println);
多条件过滤:
用Predicate接口时候可能需要进行判断条件的拼接。
- 而and方法相当于是使用&&来拼接两个判断条件;
- 而or方法相当于是使用||来拼接两个判断条件。
- negate方法相当于是在判断添加前面加了个! 表示取反
public static void main(String[] args) throws IOException {
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 20, 3333.33),
new Employee("王五", 35, 7777.77),
new Employee("赵六", 28, 6666.66),
new Employee("田七", 19, 5555.55)
);
//获取工资大于5000且年龄大于18的员工
employees.stream().filter(new Predicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 5000;
}
}.and(new Predicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 18;
}
})).forEach(System.out::println);
}
2、前向截断、后向截断
limit——截断流,使其元素不超过给定数量。 skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。
如根据分页参数截取:对list集合分页截取分页,返回当前页
//1.计算出需要跳过多少条数据(流切片的起始位置)
int startPosition = (pageNo-1)*pageSize;
List<T> allCount = service.getAllCount();
Stream<T> stream = allCount.stream().skip(startPosition).limit(pageSize);
List<T> resultAccountList = stream.collect(Collectors.toList());
//2.可以写成一行
List<AssetsDirectoriesVo> res = list.stream() .skip((pageBaseReq.getPageNum() - 1) *pageBaseReq.getPageSize()).limit(pageBaseReq.getPageSize()).collect(Collectors.toList()); //开始分页
3、排序 sorted()
sorted()——自然排序 sorted(Comparator com)——定制排序
@Test
public void test1(){
//使员工按照工资排序
employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.forEach(System.out::println);
}
4、map一对一映射
map(Function<T,R>) 输出是一个流,包含了对输入流中每个元素都应用了Function后的结果。
public void test1(){
List<String> strList = Arrays.asList("www", "atguigu", "com");
//将每一个元素与映射为大写 也可以写为 map((s) -> s.toUpperCase())
strList.stream().map(String::toUpperCase).forEach(System.out::println);
System.out.println("----------------------------");
//收集员工的姓名
employees.stream().map(Employee::getName).forEach(System.out::println);
}
mapToInt,mapToLong和mapToDouble
5、flatmap一对多映射
flatMap(Function<T,Stream<R>>)
对流扁平化处理。"flat"单词本意有平的、扁平的含义
flatMap 即对流中每个元素进行平铺后,形成多个流合在一起
map 是对流元素进行转换,flatMap 是对流中的元素进行平铺后合并,即对流中的每个元素平铺(1对多)后又转换成为了 Stream 流。
举例:需求是遍历输出所有书籍的作者名
//需求遍历输出所有书籍的作者名
ArrayList<Book> list = new ArrayList<>();
list.add(new Book("红楼梦", Arrays.asList("曹雪芹", "高鹗")));
list.add(new Book("资本论", Arrays.asList("马克思", "恩格斯")));
list.add(new Book("水浒传", Arrays.asList("施耐庵")));
list.add(new Book("资治通鉴", Arrays.asList("司马光")));
使用map
//使用map list.stream().map(book -> book.getAuthors()) .forEach(System.out::println);
打印输入如下:
这样看着好像是实现了需求
---------------------------------------------
使用flatMap
//使用flatMap list.stream().flatMap(book -> book.getAuthors().stream()) .forEach(System.out::println);
打印输出如下:
如果需求变为想要获取这些书一共有多少个作者,再用map就不合适了吧,就得用flatMap
Stream流终止操作
1、匹配,allMatch,anyMatch,noneMatch
allMatch——检查是否匹配所有元素
anyMatch——检查是否至少匹配一个元素
noneMatch——检查是否没有匹配所有元素
@Test
public void test1(){
// allMatch——检查是否匹配所有元素
boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
// anyMatch——检查是否至少匹配一个元素
boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
// noneMatch——检查是否没有匹配所有元素
boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
}
2、查找,findFirst,findAny,count,max, min
findFirst——返回第一个元素
findAny——返回当前流中的任意元素
count——返回流中元素的总个数
max——返回流中最大值
min——返回流中最小值
@Test
public void test1(){
// findFirst——返回第一个元素
Optional<Employee> op = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(op.get());
System.out.println("------------------------------------");
// findAny——返回当前流中的任意元素
Optional<Employee> op2 = employees.parallelStream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findAny();
System.out.println(op2.get());
System.out.println("-----------------------------------");
// count——返回流中元素的总个数
long count = employees.stream().count();
System.out.println(count);
System.out.println("-----------------------------------");
// max——返回流中最大值
Optional<Employee> max = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
System.out.println("-----------------------------------");
// min——返回流中最小值
Optional<Employee> min = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());
}
3、归约 reduce
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。【将流规约成一个值】
你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是规约操作的结果。
reduce ⽅法这⾥做的是:
从前两个元素开始,进⾏某种操作(我这⾥进⾏的是加法操作)后,返回⼀个结果,然后再拿这个结果跟第三个元素执⾏同样的操作,以此类推,直到最后的⼀个 元素。public static void main(String[] args) { Integer[] array = {1,2,3,4,5,6,7,8,9,10}; //使用规约对数组求和 Stream<Integer> stream = Arrays.stream(array); Optional<Integer> optional = stream.reduce((a, b) -> { System.out.println(String.format("%s: %d + %d = %d", Thread.currentThread().getName(), a, b, a + b)); return a + b; }); System.out.printf("数组array元素和:%d",optional.get()); }
默认情况下,它是在⼀个单线程运⾏的,也就是main线程。然后每次reduce操作都是串⾏起来的,⾸先计算前两个数字的和,然后再往后依次计算
改为并行后控制台打印输出,虽然计算过程看着乱乱的。。但是计算结果完全正确
4、迭代forEach
外部迭代是使用迭代器遍历
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 20, 3333.33),
new Employee("王五", 35, 7777.77),
new Employee("赵六", 28, 6666.66),
new Employee("田七", 19, 5555.55)
);
//stream().forEach() 内部迭代
employees.stream().forEach(System.out::println);
//使用集合的遍历操作 外部迭代
employees.forEach(System.out::println);
收集操作:
1、collect(Collector c)
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例
@Test
public void test1(){
//用list集合收集 Collectors.toList()
List<String> list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(list);
//用Set集合收集 Collectors.toSet()
Set<String> set = employees.stream().map(Employee::getName).collect(Collectors.toSet());
//用HashSet集合收集 Collectors.toCollection(HashSet::new)
HashSet<String> hs = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
}
2、使用Stream流实现5大聚合函数
@Test
public void test6(){
DoubleSummaryStatistics ds = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
//总工资
System.out.println(ds.getSum());
//平均值
System.out.println(ds.getAverage());
//最大值
System.out.println(ds.getMax());
//最小值
System.out.println(ds.getMin());
//个数
System.out.println(ds.getCount());
}
3、分组 根据某属性值对流分组
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 19, 3333.33),
new Employee("王五", 35, 7777.77),
new Employee("赵六", 18, 6666.66),
new Employee("田七", 19, 5555.55)
);
Map<Integer, List<Employee>> map = employees.stream().collect(Collectors.groupingBy(Employee::getAge));
map.forEach((age,employeeList)->{
System.out.println(age+"==>"+employeeList);
System.out.println("==================");
});
控制台打印
这个对集合分组,在后期的项目中就遇到了,因为是从mongodb中查询出来的医院科室数据,我们要显示一个科室树,按照大科室分组。大科室下有很多小科室
//2.根据大科室编号 bigcode 分组,获取每个大科室里面下级子科室 Map<String, List<Department>> deparmentMap = departmentList.stream().collect( Collectors.groupingBy(Department::getBigcode) );
没错,Department就是科室,下图就是mongodb存储的科室信息
连接流中每个字符串
@Test
public void test8(){
String str = employees.stream().map(Employee::getName).collect(Collectors.joining(",", "【", "】"));
System.out.println(str);
}
jdk1.7 Fork/Join框架
多线程优势:多线程尽可能去利用cpu的资源
将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
-------工作窃取模式【随机在别的线程偷一个任务执行】
并行流 并行流底层原理是 Fork/Join框架 ,尽可能去利用cpu的资源
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
4、list转map
Map<String, SysRoleDto> roleMap = null;
List<SysRoleDto> roleDtoList = null;
if (ids.size() > 0) {
roleDtoList = projectUserRoleDaoMapper.selectUserRoleInfoByUserIds(ids);
//key冲突时key的选择(这里是选择第二个key覆盖第一个key)
roleMap = roleDtoList.stream().collect(Collectors.toMap(SysRoleDto::getUseUserId, Function.identity(), (key1, key2) -> key2));
}