Lambda表达式

代码链接:

薛雄辉/springbootdemo - Gitee.com

一、Lambda表达式的引入

package com.daxiong.lambda.lead;

public class Employee {

	private int id;
	private String name;
	private int age;
	private double salary;

	public Employee() {
	}

	public Employee(String name) {
		this.name = name;
	}

	public Employee(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public Employee(int id, String name, int age, double salary) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public String show() {
		return "测试方法引用!";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		long temp;
		temp = Double.doubleToLongBits(salary);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (age != other.age)
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (Double.doubleToLongBits(salary) != Double.doubleToLongBits(other.salary))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}

}

普通方式筛选数据:

package com.daxiong.lambda.lead;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * <p>使用普通for循环筛选集合中符合条件的数据</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 16:26
 */
public class Test1 {
    static List<Employee> emps = Arrays.asList(
            new Employee(101, "张三", 18, 9999.99),
            new Employee(102, "李四", 59, 6666.66),
            new Employee(103, "王五", 28, 3333.33),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(105, "田七", 38, 5555.55)
    );

    /**
     * 获取公司中年龄小于 35 的员工信息
     * @param emps
     * @return
     */
    public static List<Employee> filterEmployeeAge(List<Employee> emps) {
        ArrayList<Employee> list = new ArrayList<>();
        for (Employee emp : emps) {
            if (emp.getAge() < 35) {
                list.add(emp);
            }
        }
        return  list;
    }

    /**
     * 取公司中工资大于 5000 的员工信息
     * @param emps
     * @return
     */
    public  static  List<Employee> filterEmployeeSalary(List<Employee> emps){
        List<Employee> list = new ArrayList<>();

        for (Employee emp : emps) {
            if(emp.getSalary() >= 5000){
                list.add(emp);
            }
        }

        return list;
    }


    public static void main(String[] args) {
        List<Employee> employees = filterEmployeeAge(emps);
        for (Employee employee : employees) {
            System.out.println(employee);
        }
        System.out.println("------------------------");
        List<Employee> employees1 = filterEmployeeSalary(emps);
        for (Employee employee : employees1) {
            System.out.println(employee);
        }
    }
}

策略模式改进普通循环方式写的代码

package com.daxiong.lambda.lead;

/**
 * <p>策略模式的公共接口</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 21:50
 */
@FunctionalInterface
public interface MyPredicate<T> {
    public   Boolean  test(T  t);
}
package com.daxiong.lambda.lead;

/**
 * <p>具体的策略</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 21:52
 */
public class FilterEmployeeForAge implements  MyPredicate<Employee>{
    /**
     * 查询年龄小于35岁的employee
     * @param employee
     * @return
     */
    @Override
    public Boolean test(Employee employee) {
        return employee.getAge()<35;
    }
}
package com.daxiong.lambda.lead;

/**
 * <p>TODO</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 21:53
 */
public class FilterEmployeeForSalary implements  MyPredicate<Employee>{
    /**
     * 查询薪水大于等于5000的employee
     * @param employee
     * @return
     */
    @Override
    public Boolean test(Employee employee) {
        return employee.getSalary()>=5000;
    }
}
package com.daxiong.lambda.lead;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * <p>策略设计模式,具体做法是:写一个公共的接口,要实现功能的类重写抽象方法以实现不同的功能,这就是策略模式</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 21:49
 */
public class Test2 {
    static List<Employee> emps = Arrays.asList(
            new Employee(101, "张三", 18, 9999.99),
            new Employee(102, "李四", 59, 6666.66),
            new Employee(103, "王五", 28, 3333.33),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(105, "田七", 38, 5555.55)
    );

    /**
     * 根据接口写一个公共的策略方法
     *
     * @param employees employee的集合
     * @param predicate 策略模式的公共接口
     * @return
     */
    public static List<Employee> filterEmployee(List<Employee> employees, MyPredicate<Employee> predicate) {
        ArrayList<Employee> result = new ArrayList<>();
        for (Employee employee : employees) {
            if (predicate.test(employee)) {
                result.add(employee);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        // 根据年龄筛选数据
        List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());
        for (Employee employee : list) {
            System.out.println(employee);
        }
        System.out.println("-------------------");
        // 根据薪资筛选数据
        List<Employee> list1 = filterEmployee(emps, new FilterEmployeeForSalary());
        for (Employee employee : list1) {
            System.out.println(employee);
        }
    }
}

通过Lambda表达式来筛选数据

package com.daxiong.lambda.lead;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * <p>通过lambda表达式的方式</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/6 22:02
 */
public class Test3 {
    static List<Employee> emps = Arrays.asList(
            new Employee(101, "张三", 18, 9999.99),
            new Employee(102, "李四", 59, 6666.66),
            new Employee(103, "王五", 28, 3333.33),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(105, "田七", 38, 5555.55)
    );

    public static void main(String[] args) {
        // 查找年龄小于35岁的employee
        emps.stream().filter((employee -> {
            return   employee.getAge()<35;
        })).forEach((employee -> {
            System.out.println(employee);
        }));

        System.out.println("----------------------");
        // 查询工资大于5000的employee
        emps.stream().filter((employee -> {
            return employee.getSalary()>5000;
        })).forEach(employee -> {
            System.out.println(employee);
        });
    }
}

二、什么是Lambda表达式

Lambda表达式就是为了简化函数式接口。函数式接口就是接口中只有一个抽象方法,在接口上有@FunctionalInterface注解。

三、Java中常见的四个函数式接口

消费型接口:有进无出

package com.daxiong.lambda.lead;

import java.util.function.Consumer;

/**
 * <p>java内置函数式接口-消费性接口,有进无出</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 14:58
 */
public class Test4 {

    public static void main(String[] args) {
        double money = 100;
//        Consumer<Double>  consumer=new Consumer<Double>() {
//            @Override
//            public void accept(Double aDouble) {
//                System.out.println("我是消费性接口,我消费的金额是:" + aDouble);
//            }
//        };
        Consumer<Double>  consumer=(x)->{
            System.out.println("我是消费性接口,我消费的金额是:" + x);
        };
        consumer.accept(money);

    }
}

供给型接口:无中生有

package com.daxiong.lambda.lead;

import java.util.function.Supplier;

/**
 * <p>供给型接口:无中生有</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 15:21
 */
public class Test5 {

    public static void main(String[] args) {
//        Supplier<Double>  supplier=new Supplier<Double>() {
//            @Override
//            public Double get() {
//                return Math.random()*100;
//            }
//        }
        Supplier<Double>  supplier=()->{
            return   Math.random()*100;
        };


        Double aDouble = supplier.get();
        System.out.println(aDouble);

    }
}

函数型接口:有进有出

package com.daxiong.lambda.lead;

import java.util.function.Function;

/**
 * <p>函数型接口:有进有出</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 15:31
 */
public class Test6 {
    public static void main(String[] args) {
        String  msg="\t\t\t需要处理的字符串";
//        Function<String,String> function=new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s.trim();
//            }
//        };
        Function<String,String> function=(x)->{
            return   x.trim();
        };

        String s = function.apply(msg);
        System.out.println(s);
    }
}

断言型接口:有进返布尔

package com.daxiong.lambda.lead;

import java.util.function.Predicate;

/**
 * <p>断言型接口:有进返布尔</p>
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 15:40
 */
public class Test7 {

    public static void main(String[] args) {
        String  msg="adaffadfadfadfd";
//        Predicate<String>  predicate=new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.length()>5;
//            }
//        };
        Predicate<String>  predicate=(s)->{
            return   s.length()>5;
        };
        boolean b = predicate.test(msg);
        System.out.println(b);
    }
}

消费型接口、供给型接口、函数型接口、断言型接口,这是Java内置的四个函数型接口,由它们派生出其他的函数型接口。

四、方法引用

package com.daxiong.lambda.lead;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * <p>TODO</p>
 * 一、方法引用:若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用
 * (可以将方法引用理解为 Lambda 表达式的另外一种表现形式)
 * <p>
 * 1. 对象的引用 :: 实例方法名
 * <p>
 * 2. 类名 :: 静态方法名
 * <p>
 * 3. 类名 :: 实例方法名
 * <p>
 * 注意:
 * ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
 * ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
 * <p>
 * 二、构造器引用 :构造器的参数列表,需要与函数式接口中参数列表保持一致!
 * <p>
 * 1. 类名 :: new
 * <p>
 * 三、数组引用
 * <p>
 * 类型[] :: new;
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 16:36
 */
public class Test8 {
    public static void main(String[] args) {
        // 对象实例的引用
//        test1();

        // 类名静态方法引用        类名 :: 静态方法名
//        test2();
        // 类名 :: 实例方法名
//        test3();
        // 构造器引用
//        test4();
        // 数组引用
        test5();

    }

    private static void test5() {
        Function<Integer, String[]> fun = (args) -> new String[args];
        String[] strs = fun.apply(10);
        System.out.println(strs.length);

        System.out.println("--------------------------");

        Function<Integer, Employee[]> fun2 = Employee[] :: new;
        Employee[] emps = fun2.apply(20);
        System.out.println(emps.length);
    }

    /**
     * 构造器引用
     */
    private static void test4() {
        // 普通lambda形式的写法
        Supplier<Employee> supplier=()->{
            return  new Employee(1, "xxh", 20, 50);
        };
        System.out.println(supplier.get());
        System.out.println("-----------------------------------");
        // 使用无参构造函数创建employee对象
        Supplier<Employee> supplier1=Employee::new;
        System.out.println(supplier1.get());
    }

    /**
     * 类型实例方法引用 类名 :: 实例方法名
     */
    private static void test3() {
        // 普通lambda形式的写法
        Function<Employee, String> function = (e) -> {
            return e.show();
        };

        Employee employee = new Employee(1, "xxh", 20, 50);
        String s = function.apply(employee);
        System.out.println(s);
    }

    /**
     * 类名静态方法引用        类名 :: 静态方法名
     */
    private static void test2() {
        // 普通lambda形式的写法
        BiFunction<Double, Double, Double> biFunction = (x, y) -> {
            return Math.max(x, y);
        };
        Double aDouble = biFunction.apply(10d, 18d);
        System.out.println(aDouble);

        System.out.println("--------------------------------------------");
        // 调用Math类中的静态方法max,传入两个数,比较产生其中的最大数
        BiFunction<Double, Double, Double> biFunction1 = Math::max;
        Double aDouble1 = biFunction1.apply(10d, 18d);
        System.out.println(aDouble1);

    }


    /**
     * 对象实例的引用
     */
    private static void test1() {
        // 普通lambda形式的写法
        Employee employee = new Employee(1, "xxh", 20, 50);
        Supplier<String> supplier = () -> {
            return employee.getName();
        };
        System.out.println(supplier.get());

        System.out.println("--------------------------------------------");
        // 方法引用,对象引用的写法
        Supplier<String> supplier1 = employee::getName;
        System.out.println(supplier1.get());
    }


}

五、流 Stream

Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。

Stream可以由数组或集合创建,对流的操作分为两种:

中间操作,每次返回一个新的流,可以有多个。
终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
另外,Stream有几个特性:

stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

package com.daxiong.lambda.lead;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

/**
 * <p>流的操作</p>
 * <p>
 * 一、Stream API 的操作步骤:
 * 1. 创建 Stream
 * 2. 中间操作
 * 3. 终止操作(终端操作)
 *
 * @author xuexionghui
 * @describe
 * @date 2023/3/7 17:28
 */
public class Test9 {
    public static void main(String[] args) {
        // 创建流
//        test1();

        // 中间操作
        test2();
    }


    /**
     * 创建流
     */
    private static void test1() {
        // 1. Collection 提供了两个方法  stream() 与 parallelStream()
        ArrayList<String> list = new ArrayList<>();
        // 创建一个串行流
        Stream<String> stream = list.stream();
        // 创建一个并行流
        Stream<String> stream1 = list.parallelStream();

        // 2. 通过 Arrays 中的 stream() 获取一个数组流
        Integer[] nums = new Integer[10];
        Stream<Integer> stream2 = Arrays.stream(nums);

        // 3. 通过 Stream 类中静态方法 of()
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);
        // 消费性接口,有来无回
        stream3.forEach(s -> {
            System.out.println(s);
        });
        System.out.println("-------------------------------");
        //4. 创建无限流
        //迭代,创建一个无限流,初始值是0,然后下一次元素在前一个元素之上加2 Stream.iterate(0, (x) -> x + 2)
        // limit(10)流中只保存十个元素
        Stream<Integer> stream4 = Stream.iterate(0, (x) -> {
            return x + 2;
        }).limit(10);
        stream4.forEach(s -> {
            System.out.println(s);
        });
        System.out.println("-------------------------------");
        // 生成
        // 生产型接口,无中生有
        Stream<Double> stream5 = Stream.generate(() -> {
            return Math.random();
        }).limit(2);
        stream5.forEach((s) -> {
            System.out.println(s);
        });

    }

    static List<Employee> emps = Arrays.asList(
            new Employee(102, "李四", 59, 6666.66),
            new Employee(101, "张三", 18, 9999.99),
            new Employee(103, "王五", 28, 3333.33),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(105, "田七", 38, 5555.55)
    );

    /*
    中间操作
    	筛选与切片
		filter——接收 Lambda , 从流中排除某些元素。
		limit——截断流,使其元素不超过给定数量。
		skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
		distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
     */
    private static void test2() {
        Stream<Employee> stream = emps.stream();

        // 获取集合中的工资超过5000的employee,并打印每一位employee
//        stream.filter((e)->{
//            return  e.getSalary()>5000;
//        }).forEach((e)->{
//            System.out.println(e);
//        });

        System.out.println("-------------------------------");
        // 获取集合中工资超过5000,并且只打印两个employee
//        stream.filter(employee -> {
//            return  employee.getSalary()>5000;
//        }).limit(2)
//                .forEach(employee -> {
//                    System.out.println(employee);
//                });

        System.out.println("-------------------------------");
        // 获取集合中工资超过5000,并且只打印工资最高的前两位
//        stream.filter((employee) -> {
//            return employee.getSalary() > 5000;
//        }).sorted((e1,e2)->{
//            return -Double.compare(e1.getSalary(),e2.getSalary());
//        }).limit(2).forEach((e)->{
//            System.out.println(e);
//        });

        // 集合员工的工资按低到高进行排序,并去除第一位的员工,然后进行打印
//        System.out.println("-------------------------------");
//        stream.sorted((e1,e2)->{
//            return   Double.compare(e1.getSalary(),e2.getSalary());
//        }).skip(1).forEach((e)->{
//            System.out.println(e);
//        });

        // 对集合中的员工信息筛选出工资大于5000的,并且进行去重,并打印每一位employee
//        stream.filter((e)->{
//            return  e.getSalary()>5000;
//        }).distinct()
//                .forEach((e)->{
//                    System.out.println(e);
//                });

        //所有的中间操作不会做任何的处理
        Stream<Employee> stream1 = stream.filter((e) -> {
            System.out.println("测试中间操作");
            return e.getAge() <= 35;
        });
        //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
        stream1.forEach(System.out::println);

    }


}

六、流的详细使用

stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:

如果流中的数据量足够大,并行流可以加快处速度。

除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:

Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();
3 Stream的使用
在使用stream之前,先理解一个概念:Optional 。

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
更详细说明请见:菜鸟教程Java 8 Optional类


案例使用的员工类
这是后面案例中使用的员工类:

List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));

class Person {
    private String name;  // 姓名
    private int salary; // 薪资
    private int age; // 年龄
    private String sex; //性别
    private String area;  // 地区

    // 构造方法
    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;
    }
 

}
3.1 遍历/匹配(foreach/find/match)
Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);

        // 遍历输出符合条件的元素
        list.stream().filter(x -> x > 6).forEach(System.out::println);
        // 匹配第一个
        Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
        // 匹配任意(适用于并行流)
        Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
        // 是否包含符合特定条件的元素
        boolean anyMatch = list.stream().anyMatch(x -> x > 6);
        System.out.println("匹配第一个值:" + findFirst.get());
        System.out.println("匹配任意一个值:" + findAny.get());
        System.out.println("是否存在大于6的值:" + anyMatch);
    }
}


3.2 筛选(filter)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
案例一:筛选出Integer集合中大于7的元素,并打印出来

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
        Stream<Integer> stream = list.stream();
        stream.filter(x -> x > 7).forEach(System.out::println);
    }
}

案例二: 筛选员工中工资高于8000的人,并形成新的集合。 形成新集合依赖collect(收集),后文有详细介绍。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName)
                .collect(Collectors.toList());
        System.out.print("薪资高于8000美元的员工:" + fiterList);
    }
}

3.3 聚合(max/min/count)
max、min、count这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。
案例一:获取String集合中最长的元素。

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");

        Optional<String> max = list.stream().max(Comparator.comparing(String::length));
        System.out.println("最长的字符串:" + max.get());
    }
}

案例二:获取Integer集合中的最大值。

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);

        // 自然排序
        Optional<Integer> max = list.stream().max(Integer::compareTo);
        // 自定义排序(从大到小排序)
        Optional<Integer> max2 = list.stream().max((o1, o2) -> o2 - o1);
        System.out.println("自然排序的最大值:" + max.get());
        System.out.println("自定义排序的最大值:" + max2.get());
    }
}

案例三:获取员工薪资最高的人。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
        System.out.println("员工薪资最大值:" + max.get().getSalary());
    }
}

案例四:计算Integer集合中大于6的元素的个数。

import java.util.Arrays;
import java.util.List;

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);

        long count = list.stream().filter(x -> x > 6).count();
        System.out.println("list中大于6的元素个数:" + count);
    }
}

3.4 映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。


案例一:英文字符串数组的元素全部改为大写。整数数组每个元素+3。

public class StreamTest {
    public static void main(String[] args) {
        String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
        List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());

        List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
        List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());

        System.out.println("每个元素大写:" + strList);
        System.out.println("每个元素+3:" + intListNew);
    }
}

案例二:将员工的薪资全部增加1000。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        // 不改变原来员工集合的方式
        List<Person> personListNew = personList.stream().map(person -> {
            Person personNew = new Person(person.getName(), 0, 0, null, null);
            personNew.setSalary(person.getSalary() + 10000);
            return personNew;
        }).collect(Collectors.toList());
        System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());

        // 改变原来员工集合的方式
        List<Person> personListNew2 = personList.stream().map(person -> {
            person.setSalary(person.getSalary() + 10000);
            return person;
        }).collect(Collectors.toList());
        System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
        System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
    }
}

案例三:将两个字符数组合并成一个新的字符数组。

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
        List<String> listNew = list.stream().flatMap(s -> {
            // 将每个元素转换成一个stream
            String[] split = s.split(",");
            Stream<String> s2 = Arrays.stream(split);
            return s2;
        }).collect(Collectors.toList());

        System.out.println("处理前的集合:" + list);
        System.out.println("处理后的集合:" + listNew);
    }
}

此外,map系列还有mapToInt、mapToLong、mapToDouble三个函数,它们以一个映射函数为入参,将流中每一个元素处理后生成一个新流。以mapToInt为例,看两个示例:

public static void main(String[] args)  {
    // 输出字符串集合中每个字符串的长度
    List<String> stringList = Arrays.asList("mu", "CSDN", "hello",
            "world", "quickly");
    stringList.stream().mapToInt(String::length).forEach(System.out::println);
    // 将int集合的每个元素增加1000
    List<Integer> integerList = Arrays.asList(4, 5, 2, 1, 6, 3);
    integerList.stream().mapToInt(x -> x + 1000).forEach(System.out::println);
}

mapToInt三个函数生成的新流,可以进行很多后续操作,比如求最大最小值、求和、求平均值:

public static void main(String[] args) {
    List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0, 4.0, 2.0);
    double average = doubleList.stream().mapToDouble(Number::doubleValue).average().getAsDouble();
    double sum = doubleList.stream().mapToDouble(Number::doubleValue).sum();
    double max = doubleList.stream().mapToDouble(Number::doubleValue).max().getAsDouble();
    System.out.println("平均值:" + average + ",总和:" + sum + ",最大值:" + max);
}

3.5 归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
案例一:求Integer集合的元素之和、乘积和最大值。

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
        // 求和方式1
        Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
        // 求和方式2
        Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
        // 求和方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);
        
        // 求乘积
        Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

        // 求最大值方式1
        Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值写法2
        Integer max2 = list.stream().reduce(1, Integer::max);

        System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
        System.out.println("list求积:" + product.get());
        System.out.println("list求最大值:" + max.get() + "," + max2);
    }
}


输出结果:

list求和:29,29,29
list求积:2112
list求最大值:11,11

案例二:求所有员工的工资之和和最高工资。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        // 求工资之和方式1:
        Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        // 求工资之和方式2:
        Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
                (sum1, sum2) -> sum1 + sum2);
        // 求工资之和方式3:
        Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);

        // 求最高工资方式1:
        Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                Integer::max);
        // 求最高工资方式2:
        Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                (max1, max2) -> max1 > max2 ? max1 : max2);
        // 求最高工资方式3:
        Integer maxSalary3 = personList.stream().map(Person::getSalary).reduce(Integer::max).get();

        System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
        System.out.println("最高工资:" + maxSalary + "," + maxSalary2 + "," + maxSalary3);
    }
}

3.6 收集(collect)
collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect主要依赖java.util.stream.Collectors类内置的静态方法。

3.6.1 归集(toList/toSet/toMap)
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。

下面用一个案例演示toList、toSet和toMap:

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
        List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
        Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());

        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        
        Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
                .collect(Collectors.toMap(Person::getName, p -> p));
        System.out.println("toList:" + listNew);
        System.out.println("toSet:" + set);
        System.out.println("toMap:" + map);
    }
}

3.6.2 统计(count/averaging)
Collectors提供了一系列用于数据统计的静态方法:

计数:count
平均值:averagingInt、averagingLong、averagingDouble
最值:maxBy、minBy
求和:summingInt、summingLong、summingDouble
统计以上所有:summarizingInt、summarizingLong、summarizingDouble
案例:统计员工人数、平均工资、工资总额、最高工资。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

        // 求总数
        Long count = personList.stream().collect(Collectors.counting());
        // 求平均工资
        Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
        // 求最高工资
        Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
        // 求工资之和
        Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
        // 一次性统计所有信息
        DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));

        System.out.println("员工总数:" + count);
        System.out.println("员工平均工资:" + average);
        System.out.println("员工工资总和:" + sum);
        System.out.println("员工工资所有统计:" + collect);
    }
}

3.6.3 分组(partitioningBy/groupingBy)
分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。


案例:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, "male", "New York"));
        personList.add(new Person("Jack", 7000, "male", "Washington"));
        personList.add(new Person("Lily", 7800, "female", "Washington"));
        personList.add(new Person("Anni", 8200, "female", "New York"));
        personList.add(new Person("Owen", 9500, "male", "New York"));
        personList.add(new Person("Alisa", 7900, "female", "New York"));

        // 将员工按薪资是否高于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);
    }
}


3.6.4 接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

        String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
        System.out.println("所有员工的姓名:" + names);
        List<String> list = Arrays.asList("A", "B", "C");
        String string = list.stream().collect(Collectors.joining("-"));
        System.out.println("拼接后的字符串:" + string);
    }
}

3.6.5 归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

        // 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
        Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
        System.out.println("员工扣税薪资总和:" + sum);

        // stream的reduce
        Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        System.out.println("员工薪资总和:" + sum2.get());
    }
}

3.7 排序(sorted)
sorted,中间操作。有两种排序:

sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):Comparator排序器自定义排序
案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序

public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();

        personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
        personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
        personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 8800, 26, "male", "New York"));
        personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

        // 按工资升序排序(自然排序)
        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);
    }
}

3.8 提取/组合
流也可以进行合并、去重、限制、跳过等操作。


public class StreamTest {
    public static void main(String[] args) {
        String[] arr1 = { "a", "b", "c", "d" };
        String[] arr2 = { "d", "e", "f", "g" };

        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);
        // concat:合并两个流 distinct:去重
        List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
        // limit:限制从流中获得前n个数据
        List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
        // skip:跳过前n个数据
        List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

        System.out.println("流合并:" + newList);
        System.out.println("limit:" + collect);
        System.out.println("skip:" + collect2);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值