Java8特性总结——恕我直言你可能真的不会java系列学习笔记

Java8特性总结

——恕我直言你可能真的不会java系列学习笔记

字母哥讲:恕我直言你可能真的不会java系列的学习笔记,以下是学习资料直达地址
视频:https://www.bilibili.com/video/BV1sE411P7C1
文章:https://www.kancloud.cn/hanxt/javacrazy/1568811
主讲: java8基础特性
恕我直言你可能真的不会java系列-lambda、streamAPI、文本块等特性深入讲解
学习笔记源码:https://github.com/gaogushenling/lambda-StreamAPI

01.lambda表达式会用了么

lambda表达式表达接口函数的实现

一、接口定义

1、经典OOP的实现样式
public class LambdaDemo {
    //函数定义
    public void printSomething(String something) {
        System.out.println(something);
    }
    //通过创建对象调用函数
    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String something = "I am learning Lambda";
        demo.printSomething(something);
    }
}
2、创建功能接口,并对该接口定义抽象方法
public class LambdaDemo {
    //抽象功能接口
    interface Printer {
        void print(String val);
    }
    //通过参数传递功能接口
    public void printSomething(String something, Printer printer) {
        printer.print(something);
    }
}

二、传统的接口函数实现方式

public static void main(String[] args) {
    LambdaDemo demo = new LambdaDemo();
    String something = "I am using a Functional interface";
    //实现Printer接口
    Printer printer = new Printer() {
        @Override
        public void print(String val) {
            //控制台打印
            System.out.println(val);
        }
    };
    demo.printSomething(something, printer);
}

三、lambda表示式实现方式

lambda表达式的语法:

(param1,param2,param3 ...,paramN)-  > {   //代码块;  }

首先我们知道lambda表达式,表达的是接口函数
箭头左侧是函数的逗号分隔的形式参数列表
箭头右侧是函数体代码

public static void main(String[] args) {
    LambdaDemo demo = new LambdaDemo();
    String something = "I am learning Lambda";
    //实现Printer接口(请关注下面这行lambda表达式代码)
    Printer printer = (String toPrint)->{System.out.println(toPrint);};
    //调用接口打印
    demo.printSomething(something, printer);
}

例子

package main.java;

public class LambdaDemo1 {
    interface Printer{
        void printer(String val);
    }
    public void pringSomething(String something,Printer printer){
        printer.printer(something);
    }

    public static void main(String[] args) {
        LambdaDemo1 lambdaDemo1 = new LambdaDemo1();
        String some = "asdasasa";

        //不使用lambda表达式
        Printer printer = new Printer() {
            @Override
            public void printer(String val) {
                System.out.println(val);
            }
        };
        lambdaDemo1.pringSomething(some,printer);

        /*1、使用lambda表达式
        *接口匿名实现类,简化函数
        * ①参数
        * ②函数体
        * */
        Printer printer1 = (String val) ->{
            System.out.println(val);
        };

        //1.1、进一步简化,去掉参数类型
        Printer printer2 = (val) ->{
            System.out.println(val);
        };
        //1.2、进一步简化,去掉参数括号(前提:只有一个参数)
        Printer printer3 = val ->{
            System.out.println(val);
        };
        //1.3、进一步简化,去掉函数体大括号(前提:函数体只有一行代码)
        Printer printer4 = val -> System.out.println(val);

        //1.4、最后可精简为如下,会自动推断lambda传入的参数类型
        lambdaDemo1.pringSomething(some,val -> System.out.println(val));

        //1.5、无参数传入的话,可以简写成
//        () -> System.out.println("无参");
        /*
        * 总结:
        * 省略类型:自动推断
        * 省略括号:一个参数
        * 
        * lambda表达式表达的是接口函数,
        * 箭头左侧是函数参数,箭头右侧是函数体。
        * 函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。
        * */
    }
}

02.Stream代替for循环(初识Stream-API)

认识

Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。

管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。

图一

例子 Stream代替for循环

package main.java;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamDemo1 {
    public static void main(String[] args) {
        List<String> letters = Arrays.asList("e", "c", "cbn", "zxc", "caz", "d", "b", "a");

        //不使用StreamAPI
       /* for (String letter : letters) {
                                //转换大写
            String temp = letter.toLowerCase();
            //排序
            //转
            //总之非常麻烦
        }*/

        //1、集合 使用StreamAPI
        //数组转换成流
        List<String> lettertsNew = letters.stream()
                //过滤:找出以c开头的元素
                .filter(s -> s.startsWith("c"))
                //将过滤后的结果全部大写,其中::为函数引用
                .map(String::toUpperCase)
                //排序,可写排序规则
                .sorted()
                //将流转换成集合
                .collect(Collectors.toList());

        System.out.println(lettertsNew);

        //2、数组转换成流
        String[] letters2 = {"e", "c", "cbn", "zxc", "caz", "d", "b", "a"};
        Stream.of(letters2).filter(s -> s.startsWith("c")).map(String::toUpperCase).sorted().toArray(String[]::new);

        //3、set转成流:集合类是一样的
        Set<String> lettersSet = new HashSet<>(letters);
        List<String> lettertsNewSet = lettersSet.stream()
                //过滤:找出以c开头的元素
                .filter(s -> s.startsWith("c"))
                //将过滤后的结果全部大写,其中::为函数引用
                .map(String::toUpperCase)
                //排序,可写排序规则
                .sorted()
                //将流转换成集合
                .collect(Collectors.toList());

        //4、文本文件转换成流
        Paths.get("file.text");
        try {
            Stream<String> stringStream = Files.lines(Paths.get("file.text"));
            List<String> f = stringStream
                    //过滤:找出以c开头的元素
                    .filter(s -> s.startsWith("c"))
                    //将过滤后的结果全部大写,其中::为函数引用
                    .map(String::toUpperCase)
                    //排序,可写排序规则
                    .sorted()
                    //将流转换成集合
                    .collect(Collectors.toList());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

题外话 介绍双冒号操作符

双冒号运算就是Java中的[方法引用] Method

[方法引用]的格式是

类名::方法名

例如

1.表达式:
person -> person.getName();
可以替换成:
Person::getName

2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new

方法引用的种类

方法引用有四种,分别是:

指向静态方法的引用

指向某个对象的实例方法的引用

指向某个类型的实例方法的引用

指向构造方法的引用

图二

其实,JVM 本身并不支持指向方法引用,过去不支持,现在也不支持。

Java 8 对方法引用的支持只是编译器层面的支持,虚拟机执行引擎并不了解方法引用。编译器遇到方法引用的时候,会像上面那样自动推断出程序员的意图,将方法引用还原成接口实现对象,或者更形象地说,就是把方法引用设法包装成一个接口实现对象,这样虚拟机就可以无差别地执行字节码文件而不需要管
什么是方法引用了。

需要注意的是,方法引用是用来简化接口实现代码的,并且凡是能够用方法引用来简化的接口,都有这样的特征:有且只有一个待实现的方法。这种接口在 Java 中有个专门的名称: 函数式接口。当你用试图用方法引用替代一个非函数式接口时,会有这样的错误提示: xxx is not a functional interface。

3.Stream的filter与谓语逻辑

什么是谓词逻辑?

WHERE 和 AND 限定了主语employee是什么,那么WHERE和AND语句所代表的逻辑就是谓词逻辑

SELECT *
FROM employee
WHERE age > 70
AND gender = 'M'

谓词逻辑的复用

filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。
比如:将下面的谓词逻辑定义在Employee实体class中。

 public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
   public static Predicate<Employee> genderM = x -> x.getGender().equals("M");

and语法(并集)
or语法(交集)
negate语法(取反)

例子

package model;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.function.Predicate;


@Data
@AllArgsConstructor
//员工
public class Employee {

    private Integer id;
    private Integer age;   //年龄
    private String gender;  //性别
    private String firstName; //名字
    private String lastName; //姓氏

    //Predicate接口,在英语中这个单词的意思是:谓词
    //谓词逻辑的复用如下
    //e -> e.getAge() > 70 && e.getGender().equals("M")
    public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
    public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
}
package model;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterPredicate {
    
    public static void main(String[] args){
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");


        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

        List<Employee> filtered = employees.stream()
                //filter(写谓词逻辑) 年龄大于70 并且是男性
                .filter(e -> e.getAge() > 70 && e.getGender().equals("M"))
                .collect(Collectors.toList());

        //使用可复用谓词逻辑
        List<Employee> filteredAnd = employees.stream()
                .filter(Employee.ageGreaterThan70
                        .and(Employee.genderM))
                .collect(Collectors.toList());

        //or
        List<Employee> filteredOr = employees.stream()
                .filter(Employee.ageGreaterThan70
                        .or(Employee.genderM))
                .collect(Collectors.toList());

        //negate 取反
        List<Employee> filteredNegate = employees.stream()
                .filter(
                        Employee.ageGreaterThan70
                                .or(Employee.genderM)
                                .negate()
                )
                .collect(Collectors.toList());


        System.out.println("filtered="+filtered);
        System.out.println("filteredAnd="+filteredAnd);
        System.out.println("filteredOr="+filteredOr);
        System.out.println("filteredNegate="+filteredNegate);
    }

}

4.Stream的map数据转换(Stream管道流的map操作)

map函数的作用就是针对管道流中的每一个数据元素进行转换操作。

例子


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamMap1 {
    public static void main(String[] args) {
        List<String> alpha = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");

        //不使用Stream管道流
        List<String> alphaUpper = new ArrayList<>();
        for (String s : alpha) {
            alphaUpper.add(s.toUpperCase());
        }
        System.out.println(alphaUpper); //[MONKEY, LION, GIRAFFE, LEMUR]

        // 1、Stream管道流map的基础用法:使用Stream管道流
        List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
        //上面使用了方法引用,和下面的lambda表达式语法效果是一样的
        //List<String> collect = alpha.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());

        System.out.println(collect); //[MONKEY, LION, GIRAFFE, LEMUR]

        //2、处理非字符串类型集合元素
        List<Integer> lengths = alpha.stream()
                .map(String::length)
                .collect(Collectors.toList());

        System.out.println(lengths); //[6, 4, 7, 5]

        Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                //除了mapToInt。还有maoToLong,mapToDouble等等用法
                .mapToInt(String::length)
                .forEach(System.out::println);
    }
}

import model.Employee;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMap2 {
    public static void main(String[] args) {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");


        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

        //每人涨一岁,性别换全词
        /*List<Employee> maped = employees.stream()
            .map(e -> {
                e.setAge(e.getAge() + 1);
                e.setGender(e.getGender().equals("M")?"male":"female");
                return e;
            }).collect(Collectors.toList());*/

        List<Employee> maped = employees.stream()
                .peek(e -> {
                    e.setAge(e.getAge() + 1);
                    e.setGender(e.getGender().equals("M")?"male":"female");
                }).collect(Collectors.toList());

        System.out.println(maped);
    }
}

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

public class StreamFlatMap {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("hello", "word");
        words.stream()
                .map(w -> Arrays.stream(w.split("")))    //[[h,e,l,l,o],[w,o,r,l,d]]
                .forEach(System.out::println);

        //flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理
        words.stream()
                .flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
                .forEach(System.out::println);
    }
}

5.Stream的状态与并行操作

Stream管道流的基本操作

  1. 源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理
  2. 中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等
  3. 终端操作:作用就是将Stream管道流转换为其他的数据类型。

图三

中间操作:有状态与无状态

状态通常代表公用数据,有状态就是有“公用数据”

Limit与Skip管道数据截取
Distinct元素去重
Sorted排序
串行、并行与顺序

例子

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamState {
    public static void main(String[] args) {
        //1、Limit与Skip管道数据截取
        List<String> limitN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                .limit(2)
                .collect(Collectors.toList());
        List<String> skipN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                .skip(2)
                .collect(Collectors.toList());
        //2、Distinct元素去重
        List<String> uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .distinct()
                .collect(Collectors.toList());
        //3、Sorted排序
        List<String> alphabeticOrder = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                .sorted()
                .collect(Collectors.toList());
        //4、串行、并行与顺序
        //默认串行
        //并行操作parallel,操作无状态操作
        Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .parallel()
                .forEach(System.out::println);
    }
}

结果

Giraffe
Lion
Lemur
Lion
Monkey

Process finished with exit code 0

6.Stream性能差问号 不要人云亦云

7.像使用SQL一样排序集合

一、字符串List排序
二、整数类型List排序
三、按对象字段对List排序
四、Comparator链对List排序

例子

import model.Employee;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class SortList {

    public static void main(String[] args) {

        //Collections.sort();
        //1、字符串List排序
        List<String> cities = Arrays.asList(
                "Milan",
                "london",
                "San Francisco",
                "Tokyo",
                "New Delhi"
        );
        System.out.println(cities);
        //[Milan, london, San Francisco, Tokyo, New Delhi]

        //大小写不敏感的排序
        cities.sort(String.CASE_INSENSITIVE_ORDER);
        System.out.println(cities);
        //[london, Milan, New Delhi, San Francisco, Tokyo]

        //大小写敏感的排序
        cities.sort(Comparator.naturalOrder());
        System.out.println(cities);
        //[Milan, New Delhi, San Francisco, Tokyo, london]

        //同样我们可以把排序器Comparator用在Stream管道流中。
        cities.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);
        //Milan
        //New Delhi
        //San Francisco
        //Tokyo
        //london

        //2、整数类型List排序
        List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9);
        System.out.println(numbers); //[6, 2, 1, 4, 9]

        numbers.sort(Comparator.naturalOrder());  //自然排序
        System.out.println(numbers); //[1, 2, 4, 6, 9]

        numbers.sort(Comparator.reverseOrder()); //倒序排序
        System.out.println(numbers);  //[9, 6, 4, 2, 1]

        //3、按对象字段对List<Object>排序
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");

        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);


        //4、Comparator链对List<Object>排序
        employees.sort(Comparator.comparing(Employee::getAge));
        employees.forEach(System.out::println);

        //先按性别,再按年龄倒序
        //如果我们希望List按照年龄age的倒序排序,就使用reversed()方法
        employees.sort(
                Comparator.comparing(Employee::getGender)
                        .thenComparing(Employee::getAge)
                        .reversed()
        );
        employees.forEach(System.out::println);

        //都是正序 ,不加reversed
        //都是倒序,最后面加一个reserved
        //先是倒序(加reserved),然后正序
        //先是正序(加reserved),然后倒序(加reserved)
    }
}

结果

[Milan, london, San Francisco, Tokyo, New Delhi]
[london, Milan, New Delhi, San Francisco, Tokyo]
[Milan, New Delhi, San Francisco, Tokyo, london]
Milan
New Delhi
San Francisco
Tokyo
london
[6, 2, 1, 4, 9]
[1, 2, 4, 6, 9]
[9, 6, 4, 2, 1]
Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)

Process finished with exit code 0

8.函数式接口Comparator

含义

所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口。

二、函数式接口的特点

  • 接口有且仅有一个抽象方法,如上图的抽象方法compare
  • 允许定义静态非抽象方法
  • 允许定义默认defalut非抽象方法(default方法也是java8才有的,见下文)
  • 允许java.lang.Object中的public方法,如上图的方法equals。
  • FunctionInterface注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错

甚至可以说:函数式接口是专门为lambda表达式准备的,lambda表达式是只实现接口中唯一的抽象方法的匿名实现类。

三、default关键字

顺便讲一下default关键字,在java8之前

接口是不能有方法的实现,所有方法全都是抽象方法
实现接口就必须实现接口里面的所有方法

这就导致一个问题:当一个接口有很多的实现类的时候,修改这个接口就变成了一个非常麻烦的事,需要修改这个接口的所有实现类。

这个问题困扰了java工程师许久,不过在java8中这个问题得到了解决,没错就是default方法

default方法可以有自己的默认实现,即有方法体。
接口实现类可以不去实现default方法,并且可以使用default方法。

四、JDK中的函数式接口举例

java.lang.Runnable,
java.util.Comparator,
java.util.concurrent.Callable
java.util.function包下的接口,如Consumer、Predicate、Supplier等

五、自定义Comparator排序

 //8、自定义Comparator排序
       /* employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee em1, Employee em2) {
                if(em1.getAge() == em2.getAge()){
                    return 0;
                }
                return em1.getAge() - em2.getAge() > 0 ? -1:1;
            }
        });*/
        //简化
        employees.sort((em1,em2) -> {
            if(em1.getAge() == em2.getAge()){
                return 0;
            }
            return em1.getAge() - em2.getAge() > 0 ? -1:1;
        });
        employees.forEach(System.out::println);

9.Stream查找与匹配元素

问题

在我们对数组或者集合类进行操作的时候,经常会遇到这样的需求,比如:
是否包含某一个“匹配规则”的元素
是否所有的元素都符合某一个“匹配规则”
是否所有元素都不符合某一个“匹配规则”
查找第一个符合“匹配规则”的元素
查找任意一个符合“匹配规则”的元素

例子

import model.Employee;

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

public class Matvhfind {
    public static void main(String[] args) {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");

        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

        //不用Stream API实现,查找员工列表中是否包含年龄大于70的员工
        boolean isExistAgeThan70 = false;
        for(Employee employee:employees){
            if(employee.getAge() > 70){
                isExistAgeThan70 = true;
                break;
            }
        }
        System.out.println(isExistAgeThan70);

        //1、第一个匹配规则函数:anyMatch,判断Stream流中是否 包含 某一个“匹配规则”的元素。
        // 这个匹配规则可以是lambda表达式或者谓词。

        //使用Stream API
        boolean isExistAgeThan702 = employees.stream().anyMatch(Employee.ageGreaterThan70);
        System.out.println(isExistAgeThan702);
        //将谓词逻辑换成lambda表达式
        boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 72);
        System.out.println(isExistAgeThan72);

        //2.1、allMatch匹配规则函数:判断是够Stream流中的 所有元素都 符合某一个"匹配规则"。
        //是否所有员工的年龄都大于10岁
        boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10);
        System.out.println(isExistAgeThan10);

        //2.2、noneMatch匹配规则函数:判断是否Stream流中的 所有元素都不 符合某一个"匹配规则"。
        //是否不存在小于18岁的员工
        boolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);
        System.out.println(isExistAgeLess18);

        /**
         *  3、元素查找与Optional
         * Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。
         *
         * isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。
         * ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。我们在第3章
         * 介绍了 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda
         * 表达式。
         * T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。
         * T orElse(T other) 会在值存在时返回值,否则返回一个默认值。
         * 关于Optinal的各种函数用法请观看视频!B站观看地址
         *
         * findFirst用于查找第一个符合“匹配规则”的元素,返回值为Optional
         * findAny用于查找任意一个符合“匹配规则”的元素,返回值为Optional
         */
        //从列表中按照顺序查找第一个年龄大于40的员工
        Optional<Employee> employeeOptional
                =  employees.stream().filter(e -> e.getAge() > 40).findFirst();
        System.out.println(employeeOptional.get());

        //isPresent是否存在
       boolean is
                =  employees.stream().filter(e -> e.getAge() > 40).findFirst().isPresent();
        System.out.println(is);

        //ifPresent如果存在
        employees.stream().filter(e -> e.getAge() > 40).findFirst().ifPresent(
                        e -> System.out.println(e)
        );

        //orElse不存在给默认值
        Employee employeeOptionalOrElse =
        employees.stream().filter(e -> e.getAge() > 90).findFirst().orElse(
                        new Employee(0,0,"F","","")
        );
        System.out.println(employeeOptionalOrElse);

        //findAny 找任意一个
        Employee employeeOptionalOrElseFindAny =
                employees.stream().filter(e -> e.getAge() > 90).findAny().orElse(
                        new Employee(0,0,"F","","")
                );
        System.out.println(employeeOptionalOrElseFindAny);
    }
}

结果

true
true
true
true
false
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
true
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=0, age=0, gender=F, firstName=, lastName=)
Employee(id=0, age=0, gender=F, firstName=, lastName=)

Process finished with exit code 0

10.Stream集合元素归约

Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:

  • Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
  • Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
  • Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。

图四

注意观察上面的图,我们先来理解累加器:

  • 阶段累加结果作为累加器的第一个参数
  • 集合遍历元素作为累加器的第二个参数

Integer类型归约
String类型归约
复杂对象归约
Combiner合并器的使用
对于大数据量的集合元素归约计算,更能体现出Stream并行流计算的威力。

图五

例子

import model.Employee;

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

/**
 * Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:
 *
 * Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
 * Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
 * Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
 * 理解累加器:
 * 阶段累加结果作为累加器的第一个参数
 * 集合遍历元素作为累加器的第二个参数
 */
public class MatchFindDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        //1、Integer类型归约
        //reduce初始值为0,累加器可以是lambda表达式,也可以是方法引用。
        int result = numbers
                .stream()
                .reduce(0, (subtotal, element) -> subtotal + element);
        System.out.println(result);  //21

        int result12 = numbers
                .stream()
                .reduce(0, Integer::sum);
        System.out.println(result12); //21

        //2、String类型归约
        //不仅可以归约Integer类型,只要累加器参数类型能够匹配,可以对任何类型的集合进行归约计算。
        List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
        String result21 = letters
                .stream()
                .reduce("", (partialString, element) -> partialString + element);
        System.out.println(result21);  //abcde


        String result22 = letters
                .stream()
                .reduce("", String::concat);
        System.out.println(result22);  //ancde

        //3、复杂对象归约
        //计算所有的员工的年龄总和。
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");


        List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);

        //先用map将Stream流中的元素由Employee类型处理为Integer类型(age)。
        //然后对Stream流中的Integer类型进行归约
        Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum);
        System.out.println(total); //346

        //4、Combiner合并器的使用
        /*
        * 除了使用map函数实现类型转换后的集合归约,我们还可以用Combiner合并器来实现,这里第一次使用到了Combiner合并器。
        * 因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。
        * 这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换
        * */
        Integer total3 = employees.stream()
                .reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); //注意这里reduce方法有三个参数
        System.out.println(total3); //346

        //5、*并行流数据归约(使用合并器)
        //在进行并行流计算的时候,可能会将集合元素分成多个组计算。为了更快的将分组计算结果累加,可以使用合并器。
        Integer total2 = employees
                .parallelStream()
                .map(Employee::getAge)
                .reduce(0,Integer::sum,Integer::sum);  //注意这里reduce方法有三个参数即合并器

        System.out.println(total2); //346
    }
}

结果

21
21
abcde
abcde
346
346
346

Process finished with exit code 0

11.StreamAPI终端操作

12.java8如何排序Map

13.Stream流逐行文件处理

14.java8-forEach(持续发布中)

posted @ 2020-12-24 21:51  高谷深陵  阅读(558)  评论(0编辑  收藏  举报