一 、Stream简介
Java 8 引入了全新的 Stream API,这里的 Stream 和 I/O 流不同,它更像具有 Iterable 的集合类
,但行为和集合类又有所不同。Stream 是 Java 8 的新特性,是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作(aggregate operation)或者大批量数据操作
。Stream 是用函数式编程方式在集合类上进行复杂操作的工具
,开发者可以更容易地使用 Lambda 表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。同时,它提供串行和并行两种模式进行汇聚操作
,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
Stream 特点:
(1)Stream 流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列;
(2)Stream 相当于高级版的 Iterator;
(3)Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等非常类似;
(4)Stream 是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation)或者大批量数据操作;
(5)Stream 是用函数式编程
方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等;
(6)在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高处理效率;
Stream 注意点:
(1)Stream
不会自己存储数据。
(2)Stream不会改变原对象,他们会返回一个新的 Stream。
(3)Stream 操作是延迟的
,他们会等到需要的结果时才执行。
(4)用并行流并不一定会提高效率
,因为 jvm 对数据进行切片和切换线程也是需要时间的。
1、什么是聚合操作
在传统的 J2EE 应用中,Java 代码就必须依赖于关系型数据库的聚合操作来完成诸如客户每月平均消费金额、最昂贵的在售商品、本周完成的有效订单(排除了无效的)以及取十个数据样本作为首页推荐等需求。但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。
而 Java 的集合 API 中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用 Iterator 来遍历集合,完成相关的聚合应用逻辑,这是一种远不够高效、笨拙的方法。而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。
2、Stream 流
(1) 什么是流
1)
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator
。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作
,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。Stream 就如同一个迭代器(Iterator),单向不可往复,数据只能遍历一次,遍历过一次后即用尽了
,就好比流水从面前流过,一去不复返。
2)而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分片成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出
。Stream 的并行操作依赖于 Java 7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
3)Stream 的另外一大特点是,数据源本身可以是无限的
。
(2)流的构成
当我们使用一个流的时候,通常包括三个基本步骤:获取数据源(source)→ 数据转换 → 执行操作获取想要的结果。如下图所示:
(3)stream 流的两种操作
每次数据转换都保持原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如
图中所示,整个过程就是将 goods 元素集合作为一个 “序列”,进行一组 “流水线” 操作: goods 集合提供了元素序列的数据源;通过 stream() 方法获得 Stream,filter / sorted / limit 是一组链式数据处理,连接起来”构成 “流水线”;forEach 最终执行。
1)filter/sorted/limit 的返回值均为 Stream(类似于 Builder 模式),但它们并不立即执行,而是构成了 “流水线”,直到 forEach 最终执行,并且关闭 Stream。 因此
将 filter/sorted/limited 等能够 “连接起来”,并且返回 Stream 的方法称为 “中间操作”(Intermediate);将 forEach 等最终执行,并且关闭 Stream 的方法称为 “终止操作”(Terminal)
。
2)intermediate 操作目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用
。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果
。
3)Stream 的中间操作并不是立即执行,而是 “延迟的”、“按需计算”;完成 “终止操作” 后,Stream 将被关闭。多个中间操作可以连接起来性格一条流水线,除非流水线上触发器终止操作,否则中间操作不会执行任何的处理,而是在终止操作时一次性全部处理,成为惰性求值
。
二、Lambda 表达式
Lambda 表达式是 Java 8 中提供的一种新的特性,它支持 Java 能进行简单的“函数式编程”。 它是一个匿名函数,即没有函数名的函数。
Lambda 表达式允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
1、函数式接口
如果定义的一个接口有且只有一个抽象方法 ,这样的接口就成为函数式接口(Functional Interface)。函数式接口可以有任意个 default 或着 static 方法。
函数式接口有时候是说具有单个抽象方法的接口, 或着 SAM interfaces。
实例:
interface Foo1 {
void bar();
}
interface Foo2 {
static String bar() { // static 方法
return "baz";
}
int bar(boolean baz);
}
interface Foo3 {
default String bar() { // default 方法
return "baz";
}
void quux();
}
interface Foo4 {
static String bar1() { // static 方法
return "baz1";
}
default String bar2() { // default 方法
return "baz2";
}
void quux();
}
2、Lambda 表达式使用时机
任何函数式接口都可以使用 lambda 表达式替换。 lambda 表达式只能出现在目标类型为函数式接口的上下文中,在可以使用 lambda 表达式的地方,方法声明时必须包含一个函数式接口。
Lambda 表达式,其本质即为实现函数式接口的一种方式。
应用场景:
你在某处就真的只需要一个能做一件事情的函数而已,连它叫什么名字都无关紧要。 Lambda 表达式就可以用来做这件事
。
3、Lambda 表达式语法
参数 —> 带返回值的表达式/无返回值的处理
(parameters) -> expression // 表达式
(parameters) ->{ // 语句块
statements;
}
1)Lambda 表达式使用 () 表示没有参数。
2)如果 Lambda 表达式中只包含一个参数,可省略掉 () 。
3)如果 Lambda 表达式的主题是一段代码块,需要使用 {} ,该代码块与普通的Java代码块并无区别,也可以返回或抛出异常。
4)如果参数的类型可以由编译器推断得出可以省略参数类型,当然你也可以加上。
示例:
//1. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y;
//2. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s);
4、Lambda 表达式实现函数式接口
在 Java 8 以前的代码中,为了实现带一个方法的接口,往往需要定义一个匿名类并复写接口方法,代码显得很臃肿。
在 Lambda 出现之前,如果我们需要实现一个函数式接口可能需要下面这种方式
Foo1 foo1 = new Foo1() {
@Override
public void bar() {
System.out.println("Hello bar");
}
};
foo1.bar();
在Java 8中,对于只有一个方法的接口可以把它视为一个函数。实际上,lambda 表达式最终也被编译为一个实现类,不过语法上做了简化
。
如果改成使用 Lambda 实现函数式接口就会简单许多:
Foo1 foo1 = ()->System.out.println("Hello Lambda!~");
...
foo1.bar();
5、Lambda 表达式特性
(1)类型推导
编译器负责推导 lambda 表达式的类型
。它利用 lambda 表达式所在上下文所期待的类型进行推导, 这个被期待的类型被称为目标类型。就是说我们传入的参数可以无需指定类型
!
(2)变量捕获
在 Java 7 中,编译器对内部类中引用的外部变量(即捕获的变量)要求非常严格: 如果捕获的变量没有被声明为 final 就会产生一个编译错误。 我们现在放宽了这个限制——对于 lambda 表达式和内部类, 我们允许在其中捕获那些符合有效只读(Effectively final)的局部变量。
简单的说,如果一个局部变量在初始化后从未被修改过,那么它就符合有效只读的要求, 换句话说,加上 final 后也不会导致编译错误的局部变量就是有效只读变量。
注意:此处和final关键字一样,指的是引用不可改!(感觉没多大意义,还不是用的final)
(3)方法引用
如果我们想要调用的方法拥有一个名字,我们就可以通过它的名字直接调用它。
Comparator byName = Comparator.comparing(Person::getName);
此处无需再传入参数,lambda 会自动装配成 Person 类型进来然后执行 getName() 方法,而后返回 getName() 的 String。
方法引用有很多种,它们的语法如下:
1)静态方法引用:ClassName::methodName
2)实例上的实例方法引用:instanceReference::methodName
3)超类上的实例方法引用:super::methodName
4)类型上的实例方法引用:ClassName::methodName
5)构造方法引用:Class::new
6)数组构造方法引用:TypeName[]::new
三、 Stream,Lambda,以及方法引用实战
1 Lambda的使用
* Lambda的使用演示和方法引用的使用演示
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println("************************");
Runnable r2 = () ->System.out.println("我爱北京故宫");
r2.run();
}
@Test
public void test2(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
int compare = com1.compare(21, 99);
System.out.println(compare);
System.out.println("*******************");
//Lambda表达式的写法
Comparator<Integer> com2 = ((o1, o2) -> Integer.compare(o1,o2));
int compare2 = com1.compare(122, 99);
System.out.println(compare2);
System.out.println("*******************");
//方法引用
Comparator<Integer> com3 = Integer ::compare ;
int compare3 = com1.compare(122, 99);
System.out.println(compare2);
}
2 Lambda的具体使用方法
/**
* -> :lambda操作符 或 箭头操作符
* -> : 左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
* -> : 右边:lambda体 (其实就是重写的抽象方法的方法体)
*
* Lambda表达式的本质:作为接口的实例
* @author shkstart
* @create 2022/12/1-22:30
*/
public class LambdaTest1 {
//语法格式一: 无参,无返回值
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println("************************");
Runnable r2 = () ->System.out.println("我爱北京故宫");
r2.run();
}
//语法格式二:Lambda 需要一个参数 但是没有返回值
@Test
public void test2(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?");
System.out.println("***************************8************");
Consumer<String> con1 = (System.out::println);//(s)->{System.out.println(s);};//
con1.accept("没有区别");
System.out.println("***************************8************");
}
@Test
public void test3(){
int[] arr = {1,2,3,4};
}
@Test
public void test4(){
//Consumer<String> con1 = (System.out::println);//(s)->{System.out.println(s);};// s->{System.out.println(s);};
}
//语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test5(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare( Integer o1,Integer o2) {
// System.out.println(o1);
// System.out.println(o2);
return o1.compareTo(o2);
}
};
// int compare = com1.compare("12","21");
// System.out.println(compare);
System.out.println(com1.compare(1, 2));
System.out.println("*****************************");
Comparator<Integer> com2 = (o1,o2) -> {
// System.out.println(o1);
// System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(12, 6));
}
@Test
public void test6(){
Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(12, 6));
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12, 21));
}
public class LambdaTest2 {
@Test
public void test1(){
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("学习太累了,想去天上人间买瓶矿泉水 价格为:" + aDouble);
}
});
System.out.println("**************************");
happyTime(200,money -> System.out.println("学习太累了,想去天上人间喝了口矿泉水 价格为:" + money));
}
public void happyTime(double money, Consumer<Double> con){
con.accept(money);
}
@Test
public void test2(){
List<String> list = Arrays.asList("北京","天津","南京","普京","吴京","刘晶晶");
List<String> list1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(list1);
List<String> list2 = filterString(list,s -> s.contains("京"));
System.out.println(list2);
}
//根据给定的规则 去过滤集合中的字符串 此规则由Predicate的方法决定
public ArrayList<String> filterString(List<String> list , Predicate<String> pre){
ArrayList<String> filterList = new ArrayList<>();
for (String s : list){
if (pre.test(s)){
filterList.add(s);
}
}
return filterList;
}
3 方法引用的具体使用方法
* 方法引用的使用
*
* 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
*
* 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
* 方法引用,也是函数式接口的实例。
*
* 3. 使用格式: 类(或对象) :: 方法名
*
* 4. 具体分为如下的三种情况:
* 情况1 对象 :: 非静态方法
* 情况2 类 :: 静态方法
*
* 情况3 类 :: 非静态方法
*
* 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
* 形参列表和返回值类型相同!(针对于情况1和情况2)
*
* Created by shkstart.
*/
public class MethodRefTest {
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");
System.out.println("*******************");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
Employee emp = new Employee(1001,"Tom",23,5600);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("*******************");
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));
System.out.println("*******************");
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(12,3));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
Function<Double,Long> func = new Function<Double, Long>() {
@Override
public Long apply(Double d) {
return Math.round(d);
}
};
System.out.println("*******************");
Function<Double,Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));
System.out.println("*******************");
Function<Double,Long> func2 = Math::round;
System.out.println(func2.apply(12.6));
}
// 情况三:类 :: 实例方法 (有难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));
System.out.println("*******************");
Comparator<String> com2 = String :: compareTo;
System.out.println(com2.compare("abd","abm"));
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6() {
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","abc"));
System.out.println("*******************");
BiPredicate<String,String> pre2 = String :: equals;
System.out.println(pre2.test("abc","abd"));
}
// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
Employee employee = new Employee(1001, "Jerry", 23, 6000);
Function<Employee,String> func1 = e -> e.getName();
System.out.println(func1.apply(employee));
System.out.println("*******************");
Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(employee));
}
4 Stream的具体使用方法
4.1
public class StreamAPITest {
//通过集合
@Test
public void test1(){
// List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() :返回一个顺序流
// Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream( ) :返回一个并行流
// Stream<Employee> parallelStream = employees.parallelStream( );
}
//通过数组
@Test
public void test2(){
int [] arr = {1,2,34,5,6,7,9};
//调用Arrays类的static <T>Stream<T> stream(T[ ] array):返回一个流
IntStream stream = Arrays.stream(arr) ;
System.out.println(stream);
// Employee e1 = new Employee(1001,"Tom" );
// Employee e2 = new Employee(1002,"Jerry" );
// Employee[] arr1 = new Employee[]{e1,e2};
// Stream<Employee> stream1 = Arrays.stream(arr1);
}
//通过Stream的of()
@Test
public void test3(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
}
//创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)/遍历前10个偶数
// stream.iterate(e, t -> t + 2).limit(10).forEach(System.out: :println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
// Stream.generate(Math: :random).limit(10).forEach(System.out: :println);
}
4.2
/**
*
* 测试Stream的中间操作
*
* @author shkstart
* @create 2022/12/3-21:41
*/
public class StreamAPITest1 {
//
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
//练习:查询员工表中薪资大于7000的员工信息
stream.filter(employee -> employee.getSalary() >5000) .forEach(System.out::println);
System.out.println("**************************");
//limit 截断流 参数是几 就直接截断
list.stream().filter(employee -> employee.getSalary() >5000) .limit(3).forEach(System.out::println);
System.out.println("**************************");
//skip(n)-- 跳过元素,返回一个扔掉了前n个元素的流 若流中元素不足n个 则返回一个
list.stream().filter(employee -> employee.getSalary() >5000) .skip(3).forEach(System.out::println);
System.out.println("**************************");
//distinct()-- 筛选 通过流所生成元素的hashCode()和equals()去除重复元素
list.stream().filter(employee -> employee.getSalary() >5000) .distinct().forEach(System.out::println);
}
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
//映射
@Test
public void test2(){
List<String> list = Arrays.asList("aa","bb","ss","ww","ee","cc");
list.stream().map(l->l.toUpperCase()).forEach(System.out::println);
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().map(Employee::getName).filter(name->name.length()>3).forEach(System.out::println);
list.stream().map(StreamAPITest1::fromStringToStream).forEach(s->s.forEach(System.out::print));
System.out.println();
list.stream().flatMap(StreamAPITest1::fromStringToStream).forEach(System.out::print);
}
//排序
@Test
public void test3(){
//sorted() ——自然排序
List<Integer> list = Arrays.asList(1, 32, 54, 343, 2131, 545, 565, 342, 54, 76, 87, 43, 55, 33,21 );
list.stream().sorted().forEach(System.out::println);
// 抛异常 因为Employee类没有实现Comparable接口
// List<Employee> list1 = EmployeeData.getEmployees();
// list1.stream().sorted().forEach(System.out::println);
//sorted(Comparator com)---定制排序
List<Employee> list1 = EmployeeData.getEmployees();
list1.stream().sorted(((o1, o2) -> Integer.compare(o1.getAge(),o2.getAge()))).forEach(System.out::println);
}
}
4.3
/**
*
* 测试Stream的终止操作
*
* @author shkstart
* @create 2022/12/4-8:14
*/
public class StreamAPITest2 {
//1-匹配与查找
@Test
public void test1(){
// aLlMatch(Predicate p)检查是否匹配所有元素。
// 练习:是否所有的员工的年龄都大于18
List<Employee> list = EmployeeData.getEmployees();
boolean b = list.stream().allMatch(e -> e.getAge() > 18);
System.out.println(b);
// anyMatch(Predicate p)一检查是否至少匹配一个元素。
// 练习:是否存在员工的工资大于10000
boolean b1 = list.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(b1);
// noneMatch(Predicate p)检查是否没有匹配的元素。
// 练习:是否存在员工姓“雷”
boolean b2 = list.stream().noneMatch(e -> e.getName().startsWith("雷"));
System.out.println(b2);
// findFirst——返回第一个元素
Optional<Employee> first = list.stream().findFirst();
System.out.println(first);
// findAny—-返回当前流中的任意元素
Optional<Employee> any = list.stream().findAny();
System.out.println(any);
// count——返回流中元素的总个数
long count = list.stream().filter(e->e.getSalary()>5000).count();
System.out.println(count);
// max ( Comparator c)—返回流中最大值
// 练习:返回最高的工资:
// Optional<Employee> max = list.stream().max((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
Stream<Double> doubleStream = list.stream().map(e -> e.getSalary());
Optional<Double> max = doubleStream.max((Double::compare));
System.out.println(max);
// min( Comparator c)—返回流中最小值
// 练习:返回最低工资的员工
Optional<Employee> min = list.stream().min((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
System.out.println(min);
// forEach(Consumer c)—内部迭代 当初的next是外部迭代
// for (Employee employee : list) {
// System.out.println(employee);
// }
}
//归纳
@Test
public void test2(){
// reduce(T identity,BinaryOperator)可以将流中元素反复结合起来,得到一个值。返回一个T
// 练1:计算1-10日的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
List<Employee> list1 = EmployeeData.getEmployees();
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
// reduce(BinaryOperator)一可以将流中元素反复结合起来,得到一个值。返回Optional
// 练习2:计算公司所有员工工资的总和
// Stream<Double> doubleStream = list1.stream().map(e -> e.getSalary());
Stream<Double> doubleStream = list1.stream().map(Employee::getSalary);
Optional<Double> reduce1 = doubleStream.reduce(Double::sum);
// Optional<Double> reduce2 = doubleStream.reduce(d1,d2 -> d1 + d2);
System.out.println(reduce1);
}
//收集
@Test
public void test3(){
//collect(Collector c) 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
//练习1:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> list = EmployeeData.getEmployees();
List<Employee> collect = list.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
collect.forEach(System.out::println);
System.out.println("****************************************");
Set<Employee> collect1 = list.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
collect1.stream().forEach(System.out::println);
}
}