Lambda表达式和Stream流

JAVA8生态

  • Lambda 表达式
  • 函数式接口
  • 方法引用 / 构造器引用
  • Stream API
  • 接口中的默认方法 / 静态方法
  • 新时间日期 API
  • 其他新特性

新特性

  • 速度更快
  • 代码更少
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常 Optional (Kotlin ?)

1、Lambda

匿名内部类

@Test
public void test01(){
    Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World!");
            }
    };
    r.run();
}

Lambda

Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升

@Test
public void test02(){
    Runnable r1 = () -> {
            System.out.println("Hello Lambda");
    };
    r1.run();
}

基础语法:

  1. Java8引入了一个新的操作符: ->,该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分为2部分

  2. 左侧:Lambda表达式的参数列表

  3. 右侧:Lambda表达式中所执行的功能,即Lambda体

1、无参数,无返回值

() -> System.out.println("Hello Lambda!");

2、一个参数,无返回值

 (x) -> System.out.println(x);

3、 一个参数,小括号可以省略不写

  x -> System.out.println(x);

4、二个以上参数,有返回值,并且lambda体有多条语句

Comparator com = (x, y) -> {
  	System.out.println("函数式接口");
   	return Integer.compare(x, y);
};

 5、Lambda 体中只有一条语句, return 和 大括号都可以省略不写

Comparator com = (x, y) -> Integer.compare(x, y);

5、Lambda 表达式 参数的数据类型可以省略不写 Jvm可以自动进行 “类型推断”

 (Integer x, Integer y) -> Integer.compare(x, y);

6、Lambda表达式需要 函数式接口 的支持

函数式接口:接口中只有一个抽象方法的接口,称为函数式接口,用@FunctionalInterface修饰


案例

调用 Collections.sort() 方法,通过定制排序 比较两个 Employee (先按照年龄比,年龄相同按照姓名比),使用 Lambda 表达式作为参数传递

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    
    private Integer id;
    private String name;
    private Integer age;
    private Double salary;
}
List emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test01(){
    Collections.sort(emps, (e1, e2) -> {
        if (e1.getAge() == e2.getAge()){
            return e1.getName().compareTo(e2.getName());
        } else {
            return Integer.compare(e1.getAge(), e2.getAge());
        }
    });

    for (Employee emp : emps) {
        System.out.println(emp);
    }
}

2、函数式接口

定义:接口中只有一个抽象方法的接口 @FunctionalIterface

Java内置四大核心函数式接口:

函数式接口 参数类型 返回类型 用途
Consumer
消费型接口
T void 对类型为T的对象应用操作:void accept(T t)
Supplier
提供型接口
T 返回类型为T的对象:T get()
Function
函数型接口
T R 对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate
断言型接口
T boolean 确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)

消费型接口

@Test
public void test01(){
    //Consumer
    Consumer consumer = (x) -> System.out.println("消费型接口" + x);
    //test
    consumer.accept(100);
}

提供型接口

@Test
public void test02(){
    List list = new ArrayList<>();
    List integers = Arrays.asList(1,2,3); 
    list.addAll(integers);
    //Supplier
    Supplier supplier = () -> (int)(Math.random() * 10);
    list.add(supplier.get());
    System.out.println(supplier);
    for (Integer integer : list) {
        System.out.println(integer);
    }
}

函数型接口

@Test
public void test03(){
    //Function
    String oldStr = "abc123456xyz";
    Function function = (s) -> s.substring(1, s.length()-1);
    //test
    System.out.println(function.apply(oldStr));
}

 断言型接口

@Test
public void test04(){
    //Predicate
    Integer age = 35;
    Predicate predicate = (i) -> i >= 35;
    if (predicate.test(age)){
        System.out.println("你该退休了");
    } else {
        System.out.println("我觉得还OK啦");
    }
}

3、引用

方法引用

定义:若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”

语法格式:

  • 对象 :: 实例方法
  • 类 :: 静态方法
  • 类 :: 实例方法
public class BuilderTest {
    //对象::实例方法
    @Test
    public void test(){
        Consumer con = x -> {
            System.out.println(x);
        };
        PrintStream out = System.out;
        Consumer con1 = out::println;
        Consumer con2 = System.out::println;
        con2.accept("qwe");
    }

    //类::静态方法名
    @Test
    public void test1(){
        Comparator com = (x, y)-> Integer.compare(x,y);
        Comparator com1 = Integer::compare;
    }


    // 类::实例方法名
    @Test
    public void test2(){
        BiPredicate bp = (x,y) -> x.equals(y);
        BiPredicate bp2 = String::equals;
    }
}

注意:

  • Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
  • 若Lambda参数列表中的第一参数是实例方法的调用者,可以使用ClassName::method
Function function = new Function() {
    @Override
    public Integer apply(String s) {
        return s.hashCode();
    }
};


Function function2 = o -> o.hashCode();

Function function3 = String::hashCode;

// o -> o.hashCode() 等价与  String::hashCode

构造器引用

格式:

  • ClassName :: new
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private Integer id;
    private String name;
    private Integer age;
    private Double salary;
    
    public Employee(Integer age){
        this.age = age;
    }
}
@Test
public void test04(){
    Supplier sup1 = () -> new ArrayList();

    Supplier sup2 = ArrayList::new;

//如果有多个构造器,如何判断是调用的实体类的哪个构造器呢?
//看下面的注意,即Function(T,R)内部的函数 R apply(T t) 是1个参数,那么就会调用是1个参数的构造器。
    Function fun2 =Employee::new;
}

注意:需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致

数组引用

语法:

  • Type :: new;
    @Test
    public void test(){
        Function fun = x -> new String[x];

        Function fun2 = String[]::new;
    }

4、Stream API

Lambda表达式和Stream流_第1张图片

Stream的 3 个操作步骤

  1. 创建Stream
  2. 一个或多个中间操作,连接形成流水线
  3. 终止操作。除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

 创建Stream

@Test
    public void test(){
        // 1. 通过Collection系列集合的 stream() 方法或 parallelStream()
        List list = new ArrayList<>();
        Stream stream = list.stream();

        // 2. 通过 Arrays 类中的静态方法 stream() 获取数组流
        String[] strArr = new String[10];
        Stream stream1 = Arrays.stream(strArr);

        // 3. 通过 Stream 类中的静态方法 of()
        Stream stream2 = Stream.of("aa", "bb", "cc");

        // 4. 创建无限流,要有终止操作才有效果
        // (1)迭代
        Stream stream3 = Stream.iterate(0, (x) -> x + 2);
        stream3.forEach(System.out::println); // 不停打印,停不下来
        stream3.limit(10) // 中间操作
                .forEach(System.out::println); // 终止操作
        // (2)生成
        Stream.generate(() -> new Random().nextInt(32))
                .limit(32)
                .forEach(System.out::println);

    }

中间操作

Lambda表达式和Stream流_第2张图片

 1、筛选和切片:

Lambda表达式和Stream流_第3张图片

List emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test01(){
    emps.stream()
        .filter((x) -> x.getAge() > 35)
        .limit(3) //短路?达到满足不再内部迭代
        .distinct()
        .skip(1)
        .forEach(System.out::println);
}

 2、映射 

Lambda表达式和Stream流_第4张图片

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
@Test
public void test02(){
    List list = Arrays.asList("a", "b", "c");
    list.stream()
        .map((str) -> str.toUpperCase())
        .forEach(System.out::println);
}
public Stream filterCharacter(String str){
    List list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }

    return list.stream();
}

@Test
public void test03(){
    List list = Arrays.asList("a", "b", "c");
    Test02 test02 = new Test02();
    list.stream()
        .flatMap(test02::filterCharacter)
        .forEach(System.out::println);
}

3、排序

  • sorted():自然排序
  • sorted(Comparator c):定制排序
 public class test {
    @Test
    public void test(){
        //自然排序
        List list = Arrays.asList("aaa", "eee", "ddd", "bbb");
        list.stream().sorted().forEach(System.out::println);

        //定制排序
        List list1 = Arrays.asList(
                new Person("张三", 18, 2000.0),
                new Person("李四", 18, 5000.0),
                new Person("王五", 45, 8700.0),
                new Person("赵六", 42, 4200.0),
                new Person("陈七", 56, 13100.0)
        );
        list1.stream().sorted((p1,p2) -> {
            if (p1.getAge().equals(p2.getAge())){
                return p1.getSale().compareTo(p2.getSale());
            }else {
                return p1.getAge().compareTo(p2.getAge());
            }
        }).forEach(System.out::println);
    }
}

Lambda表达式和Stream流_第5张图片

终止操作

1、查找和匹配:

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值
@Data
public class Person {
    private String name;
    private Integer age;
    private Double sale;
    private Status status;

    public enum Status{
        FREE,
        BUSY,
        VOCATION
    };

    public Person(String name, Integer age, Double sale, Status status) {
        this.name = name;
        this.age = age;
        this.sale = sale;
        this.status = status;
    }
}
public class test {
    @Test
    public void test(){
        List list = Arrays.asList(
                new Person("张三", 18, 2000.0, Person.Status.BUSY),
                new Person("李四", 18, 5000.0,Person.Status.FREE),
                new Person("王五", 45, 8700.0,Person.Status.VOCATION),
                new Person("赵六", 42, 4200.0,Person.Status.BUSY),
                new Person("陈七", 56, 13100.0,Person.Status.BUSY)
        );
        //allMatch 检查是否匹配所有元素,返回值为Boolean类型
        boolean b = list.stream().allMatch(e -> e.getStatus().equals(Person.Status.BUSY));
        System.out.println(b);  // false

        //anyMatch 检查是否匹配至少一个元素,返回值为Boolean类型
        boolean b1 = list.stream().anyMatch(e -> e.getStatus().equals(Person.Status.BUSY));
        System.out.println(b1); // true

        //noneMatch 检查是否没有匹配所有元素,返回值为Boolean类型
        boolean b2 = list.stream().noneMatch(e -> e.getStatus().equals(Person.Status.BUSY));
        System.out.println(b2); // false

        //findFirst 返回第一个元素
        //Optional 防止空指针异常的类型,如果first为null,可以使用.orelse()方法指定一个不为空的对象
        Optional op1 = list.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSale(), e2.getSale())).findFirst();
        System.out.println(op1.get());	// Person(name=张三, age=18, sale=2000.0, status=BUSY)

        //findAny 返回当前流中的任意元素
        //parallelStream 并行流,多个进程同时去进行filter、findAny,谁先找到算谁的
        Optional op2 = list.parallelStream().filter(e -> e.getStatus().equals(Person.Status.FREE)).findAny();	
        System.out.println(op2.get());	// Person(name=李四, age=18, sale=5000.0, status=FREE)

        //count 返回流中元素的总个数
        long count = list.stream().count();
        System.out.println(count);	// 5

        //max 返回流中的最大值
        Optional max = list.stream().max((e1, e2) -> Double.compare(e1.getSale(), e2.getSale()));
        System.out.println(max.get());	// Person(name=陈七, age=56, sale=13100.0, status=BUSY)


        //min 返回流中的最小值
        //返回list中的最小工资数
        System.out.println(list.stream().map(Person::getSale).min(Double::compare).get());	// 2000.0
    }
}

2、归约

	// 求和
    List list = Arrays.asList(1, 2, 3, 4, 5);

    // Integer::sum  ----> (x, y) -> x+y
    // 一开始把 0 当作 x,然后从集合中取出一个元素当作 y,求和为 1
    // 然后把 1 再当作 x,再从集合中取出一个元素当作 y,求和为 3
    // ...
    Integer sum = list.stream()
        // 有0作为初始值,不可能为空
        .reduce(0, Integer::sum); 
    System.out.println(sum);


    Optional sum1 = list.stream()
        // 没有初始值,可能为空,所以返回 Optional 对象
        .reduce(Integer::sum);
    System.out.println(sum1.get());

3、收集

Lambda表达式和Stream流_第6张图片

@Data
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private Double salary;


    public Employee(Integer id, String name, Integer age, Double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
}
public class 收集 {
    List emps = Arrays.asList(
            new Employee(1,"张三", 10, 9999.99),
            new Employee(2,"李四", 40, 7777.77),
            new Employee(3,"王五", 30, 6666.66),
            new Employee(4,"赵六", 28, 1111.11),
            new Employee(5,"老王", 37, 4444.44)
    );

    @Test
    public void test02(){
        //放入List
        List list = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        list.forEach(System.out::println);

        //放入Set
        Set set = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        set.forEach(System.out::println);

        //放入LinkedHashSet
        LinkedHashSet linkedHashSet = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        linkedHashSet.forEach(System.out::println);
    }

    @Test
    public void test03(){
        //总数
        Long count = emps.stream()
                .collect(Collectors.counting());
        System.out.println(count);

        //平均值
        Double avg = emps.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avg);

        //总和
        Double sum = emps.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(sum);

        //最大值
        Optional max = emps.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(max.get());

        //最小值
        Optional min = emps.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(min.get());
    }

    @Test
    public void test04(){
        //分组
        Map> map = emps.stream()
                .collect(Collectors.groupingBy(Employee::getId));
        System.out.println(map);

        //多级分组
        Map>> mapMap = emps.stream()
                .collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
                    if (e.getAge() > 35) {
                        return "开除";
                    } else {
                        return "继续加班";
                    }
                })));
        System.out.println(mapMap);

        //分区
        Map> listMap = emps.stream()
                .collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
        System.out.println(listMap);
    }

    @Test
    public void test05(){
        //总结
        DoubleSummaryStatistics dss = emps.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));
        System.out.println(dss.getMax());
        System.out.println(dss.getMin());
        System.out.println(dss.getSum());
        System.out.println(dss.getCount());
        System.out.println(dss.getAverage());

        //连接
        String str = emps.stream()
                .map(Employee::getName)
                .collect(Collectors.joining("-")); //可传入分隔符
        System.out.println(str);
    }
}

并行流和顺序流 

stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。如果流中的数据量足够大,并行流可以加快处速度。

例如筛选集合中的奇数,两者的处理不同之处:

Lambda表达式和Stream流_第7张图片 ​​​​​​Lambda表达式和Stream流_第8张图片

Lambda表达式和Stream流_第9张图片

Lambda表达式和Stream流_第10张图片

 利用Fork/Join进行计算和

public class ForkJoinCalculate extends RecursiveTask {
    private static final long serialVersionUID = 12313435L;

    private long start;
    private long end;

    private static final long THRESHOLD = 10000;

    public ForkJoinCalculate(long start,long end){
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THRESHOLD){
            long sum = 0;
            for (long i = start; i <= end ; i++) {
                sum+= i;
            }
            return sum;
        }else {
            long middle = (start + end )/2;
            ForkJoinCalculate left = new ForkJoinCalculate(start,middle);
            left.fork(); //拆分子任务,同时压入线程队列
            ForkJoinCalculate right = new ForkJoinCalculate(middle + 1,end);
            right.fork();
            return left.join() + right.join();
        }
    }
}
public class TestForkJoin {

    @Test
    public void test1(){
        Instant start = Instant.now();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinCalculate forkJoinCalculate = new ForkJoinCalculate(0, 1000000000L);
        Long sum = forkJoinPool.invoke(forkJoinCalculate);
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println("消耗时间:" + Duration.between(start,end).toMillis());   //393

    }

    //普通for循环计算
    @Test
    public void test2(){
        Instant start = Instant.now();

        long sum = 0L;
        for (int i = 0; i < 1000000000L; i++) {
            sum += i;
        }
        System.out.println(sum);
        Instant end = Instant.now();

        System.out.println("消耗时间:" + Duration.between(start,end).toMillis());   //538

    }

}

Java8并行流例子

	//串行流(单线程):切换为并行流 parallel()
    //并行流:切换为串行流 sequential()
	// 结果会溢出,不需要理会。运行时,注意观察CPU状态。运算会比普通for省时
    LongStream.rangeClosed(0, 100000000L) 
        .parallel() //底层:ForkJoin
        .reduce(0, Long::sum); 

	// 如果是数据较少,串行地情况下,一般会返回第一个结果
    // 如果是并行的情况,那就不能确保是第一个。
	// 注意,使用并行流时要考虑,并行操作对结果是否有影响
    Optional emp2 = emps.parallelStream() // 并行流
        .filter((e) -> e.getStatus().equals(Employee.Status.FREE))
        .findAny();
    System.out.println(emp2.get());

你可能感兴趣的:(JAVA8,新特性,kotlin,开发语言,android)