Java 8 新特性

一、简介概述

学习视频教程:https://www.bilibili.com/vide...

1、生态

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

2、新特性

  • 速度更快

    底层数据结构进行了改进,如HashMap由数组+链表变为数组+链表+红黑树(当链表长度>8 且总容量>64时)

    内存结构也进行了改动,如剔除了堆中的永久区概念,取而代之为MetaSpace元数据

  • 代码更少——lambda表达式

    在局部内部中声明的变量不用再强制显示加上final关键字,在编译的时候会自动加。

    类型推断,如List<User> users = new ArrayList<>(); ArrayList后的<>里面不用写类型,JVM编译器通过上下文推断。但在1.8之前是会报错的。

  • 强大的 Stream API——像sql语句那样流畅编程
  • 便于并行
  • 最大化减少空指针异常 Optional

二、Lambda表达式

演进过程:垃圾冗余代码——>策略模式(面向接口编程)——>匿名内部类——>Lambda表达式 / Stream流式编程

超级小白阶段——垃圾冗余代码
public class TestLambda {

    List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999.99),
            new Employee("李四",38,5555.55),
            new Employee("王五",50,6666.66),
            new Employee("赵六",16,3333.33),
            new Employee("田七",8,7777.77)
    );

    @Test
    public void test(){
        List<Employee> list = filterEmployees(employees);
        for (Employee employee : list) {
            System.out.println(employee);
        }
        System.out.println("-------------------------");
        List<Employee> list2 = filterEmployees2(employees);
        for (Employee employee : list2) {
            System.out.println(employee);
        }
    }

    //需求:获取当前公司中员工年龄大于35的员工信息
    public List<Employee> filterEmployees(List<Employee> list){
        List<Employee> emps = new ArrayList<>();
        for (Employee e : list) {
            if (e.getAge()>35){
                emps.add(e);
            }
        }
        return emps;
    }

    //需求:获取当前公司中员工工资大于3000的员工信息
    public List<Employee> filterEmployees2(List<Employee> list){
        List<Employee> emps = new ArrayList<>();
        for (Employee e : list) {
            if (e.getSalary()>5000){
                emps.add(e);
            }
        }
        return emps;
    }
    
    //....
    //发现问题:每次一个需求都需要写一个方法,代码冗余且繁琐。
    
}
//输出结果:
//Employee(name=李四, age=38, salary=5555.55)
//Employee(name=王五, age=50, salary=6666.66)
//-------------------------
//Employee(name=张三, age=18, salary=9999.99)
//Employee(name=李四, age=38, salary=5555.55)
//Employee(name=王五, age=50, salary=6666.66)
//Employee(name=田七, age=8, salary=7777.77)
新手小白阶段——策略模式

接口

public interface MyPredicate<T> {
    public boolean test(T t);
}

对应的实现类

public class filterEmployeesByAge implements MyPredicate<Employee>{
    @Override
    public boolean test(Employee e) {
        return e.getAge()>35;
    }
}
//------------------------------------------------------------------
public class filterEmployeesBySal implements MyPredicate<Employee>{
    @Override
    public boolean test(Employee e) {
        return e.getSalary()>5000;
    }
}
    @Test
    public void test2(){
        //根据传的不同的实现类,实现不同的业务逻辑
        List<Employee> list = filterEmployee3(employees, new filterEmployeesByAge());
        for (Employee employee : list) {
            System.out.println(employee);
        }
        System.out.println("----------------------");
        List<Employee> list1 = filterEmployee3(employees, new filterEmployeesBySal());
        for (Employee employee : list1) {
            System.out.println(employee);
        }
    }

    public List<Employee> filterEmployee3(List<Employee>list,MyPredicate<Employee> mp){
        List<Employee> emps = new ArrayList<>();
        for (Employee e : list) {
            if (mp.test(e)){
                emps.add(e);
            }
        }
        return emps;
    }

//输出结果同上一一样
//发现问题:虽然只需要创建一个接口,用不同的实现类传参的方式实现不同的业务逻辑。但每个不同的需求都需要另外创建一个对应的实现类,而实现类本身里面的方法逻辑又很简单,仍然显得冗余且复杂。
匿名内部类
    public List<Employee> filterEmployee3(List<Employee>list,MyPredicate<Employee> mp){
        List<Employee> emps = new ArrayList<>();
        for (Employee e : list) {
            if (mp.test(e)){
                emps.add(e);
            }
        }
        return emps;
    }

    @Test
    public void test3(){
        //在调用的时,直接new接口实现具体的逻辑。减少类的创建
        List<Employee> list = filterEmployee3(employees, new MyPredicate<Employee>() {
            @Override
            public boolean test(Employee e) {
                return e.getSalary() > 5000;
            }
        });
        for (Employee employee : list) {
            System.out.println(employee);
        }
    }
Lambda表达式
    public List<Employee> filterEmployee3(List<Employee>list,MyPredicate<Employee> mp){
        List<Employee> emps = new ArrayList<>();
        for (Employee e : list) {
            if (mp.test(e)){
                emps.add(e);
            }
        }
        return emps;
    }

    //Lambda表达式需要函数式接口的支持
    @Test
    public void test4(){
        List<Employee> list = filterEmployee3(employees, (e) -> e.getSalary() > 5000);
        list.forEach(System.out::println);
        System.out.println("-------------Lambda表达式--------------");
        List<Employee> list1 = filterEmployee3(employees, (e) -> e.getAge() > 35);
        list1.forEach(System.out::println);
    }

Lambda语法:

  • 左边参数,右边Lambda体(功能)
  • 左右遇一括号省——若Lambda体中只有一条语句,return和大括号{}都可以省略不写。
  • 左侧推断类型省
  • 能省则省

三、Lambda练习

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

public class TestLambda2 {

    List<Employee> emps = Arrays.asList(
            new Employee("张三",18,9999.99),
            new Employee("李四",18,5555.55),
            new Employee("王五",50,6666.66),
            new Employee("赵六",16,3333.33),
            new Employee("田七",8,7777.77)
    );

    @Test
    public void test(){
        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);
        }
    }
}
//输出结果:
//Employee(name=田七, age=8, salary=7777.77)
//Employee(name=赵六, age=16, salary=3333.33)
//Employee(name=张三, age=18, salary=9999.99)
//Employee(name=李四, age=18, salary=5555.55)
//Employee(name=王五, age=50, salary=6666.66)

2、将一个字符串转换成大写和对字符串进行截取

public interface StrFun {
    public String getValue(String str);
}
    @Test
    public void test2(){
        String s = strHandler("\t\t\t\t  宇界尚城纸飞机", str -> str.trim());
        System.out.println(s);
        String s1 = strHandler("宇界尚城纸飞机", str -> str.substring(0, 4));
        System.out.println(s1);
        String s2 = strHandler("abcdefj", str -> str.toUpperCase());
        System.out.println(s2);
    }

    public String strHandler(String str,StrFun sf){
        return sf.getValue(str);
    }
//输出结果:
//宇界尚城纸飞机
//宇界尚城
//ABCDEFJ

3、对两个long型参数进行运算

public interface MyFunction<T,R> {
    public R getValue(T t1, T t2);
}
    @Test
    public void test3(){
        TwoLong(100L, 200L, (x, y) -> x + y);
        TwoLong(100L, 200L, (x, y) -> x * y);
    }

    public void TwoLong(long l1,long l2,MyFunction<Long,Long> m){
        System.out.println(m.getValue(l1,l2));
    }
//输出结果:
//300
//20000

三、四大函数式接口

可以使用@FunctionalInterface来约束函数式接口

1、函数式接口应用

@FunctionalInterface
public interface MyFun {
    public Integer getResult(Integer num);
}
    //需求:对一个数进行运算
    @Test
    public void test5(){
        Integer result = Operation(100, (x) -> x * x);
        System.out.println(result);
        Integer result1 = Operation(200, (y) -> y + 200);
        System.out.println(result1);
    }
    public Integer Operation(Integer num, MyFun op){
        return op.getResult(num);
    }
//输出结果:
//10000
//400

2、四大函数式接口

Java8 内置的四大核心函数式接口

  • Consumer<T>:消费型接口。有去无回。有一个输入参数,没有返回值。

    Consumer<T>
        void accept(T t);
  • Supplier<T>:供给型接口。没有输入参数,只有返回值。

    Supplier<T>
        T get();
  • Funtion<T,R>:函数型接口。有一个输入参数,有返回值。

    Function<T R>:
        R apply(T t);
  • Predicate<T>:断言型接口。有一个输入参数,返回值只能是布尔值

    Predicate<T>
        boolean test(T t);
1)Consumer<T> 消费型接口
    @Test
    public void test4(){
        consume(1000.99,m-> System.out.println("消费了"+ m +"元钱"));
    }
    public void consume(double money, Consumer<Double> con){
        con.accept(money);
    }
//输出结果:
//消费了1000.99元钱
2)Supplier<T> 供给型接口

需求:产生指定个数的一些整数,并放入到集合中。

    @Test
    public void test5(){
        List<Integer> numberList = getNumberList(10, () -> (int) (Math.random() * 100));
        for (Integer num : numberList) {
            System.out.print(num + "\t");
        }
    }
    public List<Integer> getNumberList(int nums, Supplier<Integer> sp){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums; i++) {
            Integer n = sp.get();
            list.add(n);
        }
        return list;
    }
//执行一次的随机结果:
//13 44    27    78    12    73    67    17    67    93
3)Funtion<T,R> 函数式接口

同Lambda练习2,可用于处理字符串

    @Test
    public void test6(){
        String s = strHandler1("\t\t\t\t  宇界尚城纸飞机", str -> str.trim());
        System.out.println(s);
        String s1 = strHandler1("宇界尚城纸飞机", str -> str.substring(0, 4));
        System.out.println(s1);
        String s2 = strHandler1("abcdefj", str -> str.toUpperCase());
        System.out.println(s2);
    }
    public String strHandler1(String str, Function<String,String> fun){
        return fun.apply(str);
    }
//输出结果:
//宇界尚城纸飞机
//宇界尚城
//ABCDEFJ
4)Predicate<T> 断言型接口

需求:将满足条件的字符串,放入到集合中

    @Test
    public void test7(){
        List<String> list = Arrays.asList("hello","www","lambda","java","redis","ok");
        List<String> strs = filterStr(list, s -> s.length() > 3);
        for (String str : strs) {
            System.out.println(str);
        }
    }
    public List<String> filterStr(List<String>list, Predicate<String> pre){
        List<String> arrayList = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)){
                arrayList.add(s);
            }
        }
        return arrayList;
    }
//输出结果:
//hello
//lambda
//java
//redis

其他子接口

image

四、方法引用与构造器引用

1、方法引用

若Lambda体中的内容有方法已经实现了,我们就可以使用“方法引用”(可以理解为方法引用就是Lambda表达式的另外一种表现形式)

主要三种语法格式:

  • 对象 ::实例方法名
  • 类::静态方法名
  • 类::实例方法名

【注意】:

  • 1、Lambda体重调用方法的参数列表和返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
  • 2、若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可使用类名::实例方法名
1) 对象::实例方法名

Lambda体重调用方法的参数列表和返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致!

    @Test
    public void test(){
        Consumer<String> con = x -> System.out.println(x);
        con.accept("lambda");

        PrintStream ps = System.out;
        Consumer<String> con1 = x -> ps.println(x);
        con1.accept("lambda1");

        //对象::实例方法名
        PrintStream ps1 = System.out;
        Consumer<String> con2 = ps1::println;
        con2.accept("methodRef");

        Consumer<String> con3 = System.out::println;
        con3.accept("方法引用的第一种语法格式");
    }
//输出结果:
//lambda
//lambda1
//methodRef
//方法引用的第一种语法格式
    @Test
    public void test1(){
        Employee emp = new Employee("张三", 18, 8000);
        Supplier<String> sup = ()-> emp.getName();
        System.out.println(sup.get());

        //对象::实例方法名
        System.out.println("=======================");
        Supplier<String> sup1 = emp::getName;
        System.out.println(sup1.get());

        System.out.println("=======================");
        Supplier<Integer> sup2 = emp::getAge;
        System.out.println(sup2.get());
    }
//输出结果:
//张三
//=======================
//张三
//=======================
//18
2) 类::静态方法名
    @Test
    public void test3(){
        Comparator<Integer> com = (x,y) ->Integer.compare(x,y);
        //类::静态方法名
        Comparator<Integer> com1 = Integer::compare;
    }
3) 类::实例方法名

若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可使用类名::实例方法名

    @Test
    public void test4(){
        BiPredicate<String,String> bip = (x,y)->x.equals(y);
        //类::实例方法名
        BiPredicate<String,String> bip1 = String::equals;
    }

2、构造器引用

语法格式:

  • 类::new

【注意】:

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

    @Test
    public void test5(){
        Supplier<Employee> sup = ()-> new Employee();
        //构造器引用
        Supplier<Employee> sup1 = Employee::new;
        System.out.println(sup1.get());
    }
//输出结果:
//Employee(name=null, age=0, salary=0.0)
    @Test
    public void test6(){
        Function<String,Employee> fun = (x)-> new Employee(x);
        //构造器引用
        Function<String,Employee> fun1 = Employee::new;//使用有一个String参数的构造器
        Employee emp = fun1.apply("纸飞机");
        System.out.println(emp);

        BiFunction<String,Integer,Employee> bif = Employee::new;//使用有两个参数且类型与接口一致的构造器
        Employee emp1 = bif.apply("纸飞机1", 18);
        System.out.println(emp1);
    }
//输出结果:
//Employee(name=纸飞机, age=0, salary=0.0)
//Employee(name=纸飞机1, age=18, salary=0.0)

3、数组引用

语法格式:

  • Type[]::new
    @Test
    public void test7(){
        Function<Integer,String[]> fun = x -> new String[x];
        String[] strs = fun.apply(10);
        System.out.println(strs.length);
        //数组引用
        Function<Integer,String[]> fun1 = String[]::new;
        String[] strs1 = fun1.apply(20);
        System.out.println(strs1.length);
    }

五、Stream流式计算

1、什么是Stream流?

Stream流式数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”

【注意】:

  • ①Stream 自己不会存储元素。
  • ②Stream 不会改变源对象。只会返回一个持有结果的新的Stream
  • ③Stream 操作是延迟执行的(懒加载)。意味着是会等到需要结果的时候才执行。

2、Stream操作的三个步骤

  • 1) 创建Stream

    一个数据源(如集合、数组),获取一个流

  • 2) 中间操作

    一个中间操作链,对数据源的数据进行处理。

  • 3) 终止操作(终端操作)

    一个终止操作,执行中间操作链,并产生结果

3、创建Stream流的四种方式

    List<Employee> employees = Arrays.asList(
        new Employee("张三",36,9999.99),
        new Employee("李四",30,5555.55),
        new Employee("王五",48,6666.66),
        new Employee("赵六",52,3333.33),
        new Employee("田七",8,7777.77)
    );

    /**
     * 创建Stream流的四种方式
     */
    @Test
    public void test(){
        //1、通过Collection系列集合提供的stream()和parallelStream()来获取流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //2、通过Arrays中的静态方法stream()获取数组流
        Employee[] emps = new Employee[10];
        Stream<Employee> stream1 = Arrays.stream(emps);

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

        //4、创建无限流
        //1)迭代,Stream中的静态方法iterate()
        Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2);
        stream3.limit(10).forEach(System.out::println);

        //2)生成,Stream中的静态方法generate()
        Stream<Double> stream4 = Stream.generate(() -> Math.random());
        stream4.limit(5).forEach(System.out::println);
    }

4、筛选与切片(中间操作 )

  • filter:接收 Lambda ,从流中排除某些元素。filter里是Predicate断言型接口。
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素。与 limit(n) 互补
  • distinct:筛选,通过流所生成的元素 hashCode() 与 equals() 取除重复元素

【注意】:只要没有执行终止操作,中间操作都不会执行

filter
    //内部迭代
    @Test
    public void test1(){
        employees.stream()
                 //中间操作:不会执行任何操作
                 .filter(e -> {
                     System.out.println("Stream API的中间操作");
                     return e.getAge()>35;
                 })
                 //终止操作:一次性执行全部内容,惰性求值
                 .forEach(System.out::println);
    }
//输出结果:
//Stream API的中间操作
//Employee(name=张三, age=36, salary=9999.99)
//Stream API的中间操作
//Stream API的中间操作
//Employee(name=王五, age=48, salary=6666.66)
//Stream API的中间操作
//Employee(name=赵六, age=52, salary=3333.33)
//Stream API的中间操作
Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”也叫做延迟加载懒加载。

内部迭代:迭代操作由Stream API完成

外部迭代:自己通过迭代器Iterator完成迭代操作

    //外部迭代
    @Test
    public void test2(){
        Iterator<Employee> it = employees.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
//输出结果:
//Employee(name=张三, age=36, salary=9999.99)
//Employee(name=李四, age=30, salary=5555.55)
//Employee(name=王五, age=48, salary=6666.66)
//Employee(name=赵六, age=52, salary=3333.33)
//Employee(name=田七, age=8, salary=7777.77)
limit

limit是短路的,即找到符合条件之后,就不再进行之后的迭代操作。

    @Test
    public void test3(){
        employees.stream()
                 .filter(e ->{
                     System.out.println("短路!");
                     return e.getSalary()>5000;
                 })
                 .limit(2)
                 .forEach(System.out::println);
    }
//输出结果:
//短路!
//Employee(name=张三, age=36, salary=9999.99)
//短路!
//Employee(name=李四, age=30, salary=5555.55)
skip
    @Test
    public void test4(){
        employees.stream()
                 .filter(e-> e.getSalary()>5000)
                 //跳过2个元素,与limit互补。
                 .skip(2)
                 .forEach(System.out::println);
    }
//输出结果:
//Employee(name=王五, age=48, salary=6666.66)
//Employee(name=田七, age=8, salary=7777.77)
distinct
    List<Employee> employees = Arrays.asList(
            new Employee("张三",36,9999.99),
            new Employee("李四",30,5555.55),
            new Employee("王五",48,6666.66),
            new Employee("赵六",52,3333.33),
            new Employee("田七",8,7777.77),
            new Employee("田七",8,7777.77),
            new Employee("田七",8,7777.77)
    );

    @Test
    public void test5(){
        employees.stream()
                 .filter(e->e.getSalary()>5000)
                 .skip(2)
                 //去除重复的元素,但是根据HashCode()和equals()进行去除。所以需要重写这两个方法
                 .distinct()
                 .forEach(System.out::println);
    }
//输出结果:
//Employee(name=王五, age=48, salary=6666.66)
//Employee(name=田七, age=8, salary=7777.77)

5、映射(中间操作 )

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
map
    @Test
    public void test6(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        list.stream()
                .map(str -> str.toUpperCase())
                .forEach(System.out::println);
        System.out.println("==========================");
        employees.stream()
//                    .map(e->e.getName())
                .map(Employee::getName)
                .forEach(System.out::print);
    }
//输出结果:
//AAA
//BBB
//CCC
//DDD
//EEE
//==========================
//张三李四王五赵六田七田七田七

特殊需求:解析字符串,并将解析后的字符都提出来放到集合中并打印出来

flatmap

【注意】:map和flatmap的区别

    @Test
    public void test7(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        Stream<Stream<Character>> stream = list.stream()
                                                //{{a,a,a},{b,b,b},{c,c,c}...}
                                                .map(TestStreamAPI::filterCharacter);
//        stream.forEach(System.out::println);
        stream.forEach(sm ->{
            sm.forEach(System.out::println);
        });
        System.out.println("=========================");

        Stream<Character> stream1 = list.stream()
                //{a,a,a,b,b,b,c,c,c...}
                .flatMap(TestStreamAPI::filterCharacter);
        stream1.forEach(System.out::println);
    }

类似add()和addAll()的区别

    @Test
    public void test8(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        List list1 = new ArrayList<>();

        list1.add("11");
        list1.add("666");
//        list1.add(list);//输出结果:[11, 666, [aaa, bbb, ccc, ddd, eee]]
        list1.addAll(list);//输出结果:[11, 666, aaa, bbb, ccc, ddd, eee]
        System.out.println(list1);
    }

6、排序(中间操作 )

  • sorted():自然排序(Comparable)
  • sorted(Comparator c):定制排序(Comparator)
    @Test
    public void test9(){
        List<String> list = Arrays.asList("ccc","aaa","eee","bbb","ddd");
        list.stream()
                //自然排序,按照Comparable的规则排序
                .sorted()
                .forEach(System.out::println);

        System.out.println("============================");
        employees.stream()
                    //自定义排序
                    .sorted((e1,e2)->{
                        if (e1.getAge()==e2.getAge()){
                            return e1.getName().compareTo(e2.getName());
                        }else{
                            return Integer.compare(e1.getAge(),e2.getAge());
                        }
                    })
                    .forEach(System.out::println);
    }
//输出结果:
//aaa
//bbb
//ccc
//ddd
//eee
//============================
//Employee(name=田七, age=8, salary=7777.77)
//Employee(name=田七, age=8, salary=7777.77)
//Employee(name=田七, age=8, salary=7777.77)
//Employee(name=李四, age=30, salary=5555.55)
//Employee(name=张三, age=36, salary=9999.99)
//Employee(name=王五, age=48, salary=6666.66)
//Employee(name=赵六, age=52, salary=3333.33)

7、查询与匹配(终止操作)

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值

先修改一下之前的Employee为如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

    private String name;
    private Integer age;
    private Double salary;
    private Status status;

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

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

    public enum Status{
        FREE,
        BUSY,
        VOCATION;
    }
}
练习测试
    List<Employee> employees = Arrays.asList(
            new Employee("张三",36,9999.99, Employee.Status.FREE),
            new Employee("李四",30,5555.55, Employee.Status.BUSY),
            new Employee("王五",48,6666.66, Employee.Status.VOCATION),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("田七",8,7777.77, Employee.Status.BUSY)
    );

    
    @Test
    public void test(){
        //allMatch:检查是否匹配所有元素
        boolean b = employees.stream()
                .allMatch(e -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b);//输出结果:false
        
        //anyMatch:检查是否至少匹配一个元素
        boolean b1 = employees.stream()
                .anyMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println(b1);//输出结果:true
        
        //noneMatch:检查是否没有匹配所有元素。不是没有匹配的元素,双重否定=存在匹配的元素
        boolean b2 = employees.stream()
                .noneMatch(e -> e.getStatus().equals(Employee.Status.VOCATION));
        System.out.println(b2);//输出结果:false
        
        //findFirst:返回第一个元素。返回的是Optional容器对象,说明findFirst可能会报空指针错误。
        Optional<Employee> op = employees.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findFirst();
        System.out.println(op.get());//输出结果:Employee(name=赵六, age=52, salary=3333.33, status=FREE)
        //若在Double前加上-,结果是最高工资的。
        
        //findAny:返回当前流中的任意元素。
        Optional<Employee> op1 = employees.stream()
                .filter(e -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(op1.get());//输出结果:Employee(name=张三, age=36, salary=9999.99, status=FREE)    
    }
    
    
    @Test
    public void test1(){
        //count:返回流中元素的总个数
        long count = employees.stream()
                .count();
        System.out.println(count);//输出结果:5
        
        //需求:获取工资最高的员工信息
        //max:返回流中最大值。
        Optional<Employee> op = employees.stream()
                .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(op.get());//输出结果:Employee(name=张三, age=36, salary=9999.99, status=FREE)
        
        //需求:获取最低员工的工资
        //min:返回流中最小值
        Optional<Double> minOp = employees.stream()
                .map(Employee::getSalary)
//                .min((e1, e2) -> Double.compare(e1, e2));
                .min(Double::compare);
        System.out.println(minOp.get());//输出结果:3333.33
        
    }

8、归约与收集(终止操作)

  • 归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值
  • 收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法
reduce归约
    @Test
    public void test2(){
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer nums = integers.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(nums);

        System.out.println("=====================");
        //需求:求出当前公司中所有员工的工资总和
        Optional<Double> total = employees.stream()
                .map(Employee::getSalary)
                .reduce(Double::sum);
        System.out.println(total.get());
    }
//输出结果:
//55
//=====================
//33333.3

map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名

collect收集

Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map),但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例。

     List<Employee> employees = Arrays.asList(
            new Employee("张三",36,9999.99, Employee.Status.FREE),
            new Employee("李四",30,5555.55, Employee.Status.BUSY),
            new Employee("王五",48,6666.66, Employee.Status.VOCATION),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("田七",8,7777.77, Employee.Status.BUSY),
            new Employee("田七",8,7777.77, Employee.Status.BUSY)
    );

    @Test
    public void test3(){

        //需求:将公司中员工的所有名字提取收集起来放入到一个集合中
        //.collect(Collectors.toList())
        List<String> list = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        list.forEach(System.out::println);
        System.out.println("==============================");

        //.collect(Collectors.toSet())
        Set<String> set = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        set.forEach(System.out::println);//元素不重复
        System.out.println("==============================");

        //放入到特殊集合中
        //.collect(Collectors.toCollection(HashSet::new))
        HashSet<String> hs = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        hs.forEach(System.out::println);
    }
 @Test
    public void test4(){
        //收集元素总数
        Long nums = employees.stream()
                .collect(Collectors.counting());
        System.out.println(nums);
        System.out.println("=========================");

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

        //收集总和
        Double total = employees.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(total);
        System.out.println("=========================");

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

        //最小值
        Optional<Double> min = employees.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(min.get());
    }
//输出结果:
//5
//=========================
//6666.660000000001
//=========================
//33333.3
//=========================
//Employee(name=张三, age=36, salary=9999.99, status=FREE)
//3333.33
    @Test
    public void test5(){
        //分组
        //Collectors.groupingBy
        Map<Employee.Status, List<Employee>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(map);

        //多级分组
        Map<Employee.Status, Map<String, List<Employee>>> map2 = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
                    if (e.getAge() <= 35) {
                        return "青年";
                    } else if (e.getAge() <= 50) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                })));
        System.out.println(map2);

        //分区
        //Collectors.partitioningBy
        Map<Boolean, List<Employee>> map3 = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
        System.out.println(map3);

        //先获取汇总
        //Collectors.summarizing...
        DoubleSummaryStatistics dss = employees.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));
        System.out.println(dss.getAverage());
        System.out.println(dss.getCount());
        System.out.println(dss.getMax());
        System.out.println(dss.getMin());
        System.out.println(dss.getSum());

        //字符串连接
        //Collectors.joining
        String str = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(",", "(", ")"));
        System.out.println(str);//输出结果:(张三,李四,王五,赵六,田七)
    }

六、Stream练习

1、题目

1、给定一个数字列表,如何返回一个由每个数的平方构成的列表?给定[1,2,3,4,5],应该返回[1,4,8,16,25]

2、怎样用map和reduce方法数一数流中有多少个Employee?

3、给定交易员类、交易类和对象集合,做相关习题

/**
 * 交易员类
 */
public class Trader {

    private String name;
    private String city;

    public Trader() {
    }

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Trader{" +
                "name='" + name + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}
/**
 * 交易类
 */
public class Transaction {

    private Trader trader;
    private int year;
    private int value;

    public Transaction() {
    }

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return trader;
    }

    public void setTrader(Trader trader) {
        this.trader = trader;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Transaction{" +
                "trader=" + trader +
                ", year=" + year +
                ", value=" + value +
                '}';
    }
}
public class TestTransaction {
    
    List<Transaction> transactions = null;

    @Before
    public void before(){
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");


        transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
    }
}
  • 1)找出2011年发生的所有交易,并按交易额排序(从低到高)
  • 2)交易员都在哪些不同的城市工作过?
  • 3)查找所有来自剑桥的交易员,并按姓名排序。
  • 4)返回所有交易员的姓名字符串,按字母顺序排序。
  • 5)有没有交易员是在米兰工作的?
  • 6)打印生活在剑桥的交易员的所有交易额
  • 7)所有交易中,最高的交易额是多少
  • 8)找到交易额最小的交易

4、

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有5个用户!筛选:
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(6,"e",25);
        // 集合就是存储
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        //计算交给Stream
        //lambda表达式、链式编程、函数式接口、Stream流式计算
    }
}

2、作答

第1、2题答案:

    List<Employee> employees = Arrays.asList(
            new Employee("张三",36,9999.99, Employee.Status.FREE),
            new Employee("李四",30,5555.55, Employee.Status.BUSY),
            new Employee("王五",48,6666.66, Employee.Status.VOCATION),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("田七",8,7777.77, Employee.Status.BUSY)
    );

    @Test
    public void test(){
        //1、给定一个数字列表,
        // 如何返回一个由每个数的平方构成的列表?给定[1,2,3,4,5],应该返回[1,4,9,16,25]
        int[] nums = new int[]{1,2,3,4,5};
        Arrays.stream(nums)
                .map(x->x * x)
                .forEach(System.out::println);
        System.out.println("===================");
        //2、怎样用map和reduce方法数一数流中有多少个Employee?
        Optional<Integer> op = employees.stream()
                .map(e -> 1)
                .reduce(Integer::sum);
        System.out.println(op.get());
    }

第3大题答案:

public class TestTransaction {

    List<Transaction> transactions = null;

    @Before
    public void before(){
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");


        transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
    }

    @Test
    public void test(){
        //1)找出2011年发生的所有交易,并按交易额排序(从低到高)
        transactions.stream()
                        .filter(t->t.getYear()==2011)
                        .sorted((t1,t2)->Integer.compare(t1.getValue(),t2.getValue()))
                        .forEach(System.out::println);
        System.out.println("=========================");
        //2)交易员都在哪些不同的城市工作过?
        transactions.stream()
                        .map(t->t.getTrader().getCity())
                        .distinct()
                        .forEach(System.out::println);
        System.out.println("=========================");
        //3)查找所有来自剑桥的交易员,并按姓名排序。
        transactions.stream()
                        .filter(t->t.getTrader().getCity().equals("Cambridge"))
                        .map(Transaction::getTrader)
                        .distinct()
                        .sorted((t1,t2)->t1.getName().compareTo(t2.getName()))
                        .forEach(System.out::println);
        System.out.println("=========================");
        //4)返回所有交易员的姓名字符串,按字母顺序排序。
        //题目存在歧义
        //解法1:
        transactions.stream()
                        .map(t->t.getTrader().getName())
                        .sorted()
                        .forEach(System.out::println);
        System.out.println("==========================");
        //解法2:
        String str = transactions.stream()
                .map(t -> t.getTrader().getName())
                .sorted()
                .reduce("", String::concat);
        System.out.println(str);
        System.out.println("==========================");
        //解法3:
        transactions.stream()
                        .map(t->t.getTrader().getName())
                        .flatMap(TestTransaction::filterCharacter)
                        .sorted((s1,s2)->s1.compareToIgnoreCase(s2))
                        .forEach(System.out::print);
    }
    public static Stream<String> filterCharacter(String strs){
        List<String> list = new ArrayList<>();
        for (Character ch : strs.toCharArray()) {
            list.add(ch.toString());
        }
        return list.stream();
    }
}
//输出结果:
//Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}
//Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400}
//=========================
//Cambridge
//Milan
//=========================
//Trader{name='Alan', city='Cambridge'}
//Trader{name='Brian', city='Cambridge'}
//Trader{name='Raoul', city='Cambridge'}
//=========================
//Alan
//Brian
//Mario
//Mario
//Raoul
//Raoul
//==========================
//AlanBrianMarioMarioRaoulRaoul
//==========================
//aaaaaAaBiiilllMMnnoooorRRrruu
    @Test
    public void test2(){
        //5)有没有交易员是在米兰工作的?
        boolean b = transactions.stream()
                .anyMatch(t -> t.getTrader().getCity().equals("Milan"));
        System.out.println(b);
        System.out.println("==========================");
        //6)打印生活在剑桥的交易员的所有交易额
        Optional<Integer> sum = transactions.stream()
                .filter(t -> t.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getValue)
                .reduce(Integer::sum);
        System.out.println(sum.get());
        System.out.println("==========================");
        //7)所有交易中,最高的交易额是多少
        Optional<Integer> max = transactions.stream()
                .map(Transaction::getValue)
                .max(Integer::compareTo);
        System.out.println(max.get());
        System.out.println("==========================");
        //8)找到交易额最小的交易
        Optional<Transaction> min = transactions.stream()
                .min((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()));
        System.out.println(min.get());
    }
//输出结果:
//true
//==========================
//2650
//==========================
//1000
//==========================
//Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}

第4大题答案:

public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(6,"e",25);
        // 集合就是存储
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        //计算交给Stream
        //lambda表达式、链式编程、函数式接口、Stream流式计算
        list.stream()
                .filter(u->u.getId()%2==0)
                .filter(u->u.getAge()>23)
                .map(u->u.getName().toUpperCase())
                .sorted((uu1,uu2)->uu2.compareTo(uu1))
                .limit(1)
                .forEach(System.out::println);
    }
}

七、并行流与串行流

1、简述

public class TestStreamAPI2 {

    List<Employee> employees = Arrays.asList(
            new Employee("张三",36,9999.99, Employee.Status.FREE),
            new Employee("李四",30,5555.55, Employee.Status.BUSY),
            new Employee("王五",48,6666.66, Employee.Status.VOCATION),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("赵六",52,3333.33, Employee.Status.FREE),
            new Employee("田七",8,7777.77, Employee.Status.BUSY)
    );

    @Test
    public void test(){
        //stream()创建的是串行流
        Optional<Employee> op1 = employees.stream()
                .filter(e -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(op1.get());//输出结果:Employee(name=张三, age=36, salary=9999.99, status=FREE)
    }

    @Test
    public void test2(){
        //parallelStream()创建的是并行流
        Optional<Employee> op2 = employees.parallelStream()
                .filter(e -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(op2.get());//输出结果:Employee(name=赵六, age=52, salary=3333.33, status=FREE)
    }
}

说明:在上述例子当中,stream()创建的串行流,它是顺序找符合条件的任意一个元素,所以它永远返回的会是张三这个员工;而parallelStream(),意味着是并行查找符号条件的任意一个元素,而此时赵六的元素要多一些,选到的概率相对较大一些,但结果仍是不确定性的。

2、ForkJoin

什么是ForkJoin?

ForkJoin是在JDK1.7出来的,作用是并行执行任务来提高效率。在大数据量下更适用!

思想:将大任务拆分成小任务执行,最后合并结果。

特点:双端队列,工作窃取机制。

但实际较少使用,因为实现起来比较复杂!其中的逻辑关系需要我们自己去编写!

RecursiveAction:递归事件,没有返回值

RecursiveTask:递归任务,有返回值

public class ForkJoinCalculate extends RecursiveTask<Long> {

    private static final long serialVersionUID = 1L;

    private long start;
    private long end;

    private static final long THRESHOLD = 10000L;

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

    @Override
    protected Long compute() {

        long length = end - start;
        if (length <= THRESHOLD){
            //小于临界值,不需要ForkJoin,直接for循环
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else {//大于临界值,进行ForkJoin
            //中间值
            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();
        }
    }
}

3、普通for循环、ForkJoin、并行流运行对比

public class TestForkJoin {
    public static void main(String[] args) {
        test();//3282
        test1();//501
        test2();//471
    }

    /**
     * 普通for循环
     */
    public static void test(){
        long start = System.currentTimeMillis();
        long sum = 0;
        for (Long i = 0L; i <= 10_0000_0000L ; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum +"时间:"+ (end - start));
    }

    /**
     * 使用ForkJoin
     */
    public static void test1(){
        long start = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinCalculate task = new ForkJoinCalculate(0L, 10_0000_0000L);
        Long sum = pool.invoke(task);
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum +"时间:"+ (end - start));
    }

    /**
     * 使用并行流
     */
    public static void test2(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0, 10_0000_0000)
                .parallel()
                .reduce(Long::sum)
                .getAsLong();
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum +"时间:"+ (end - start));
    }
}

八、Optional容器类

定义:Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;并且可以避免空指针异常

常用方法

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty(T t):创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
  • isPresent():判断是否包含某值
  • orElse(T t):如果调用对象包含值,返回该值,否则返回 t
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
  • flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional

使用测试

public class TestOptional {

    @Test
    public void test(){
        //Optional.of(T t):创建一个 Optional 实例
        Optional<Employee> op = Optional.of(new Employee());
        Employee employee = op.get();
        System.out.println(employee);
    }

    @Test
    public void test1(){
        //Optional.empty(T t):创建一个空的 Optional 实例
        Optional<Object> op = Optional.empty();
        System.out.println(op.get());//java.util.NoSuchElementException: No value present
    }

    @Test
    public void test2(){
        //Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
        //ofNullable实质:return value == null ? empty() : of(value);
        Optional<Object> op = Optional.ofNullable(new Employee());
        System.out.println(op.get());
    }

    @Test
    public void test3(){
        //isPresent():判断是否包含某值
        Optional<Object> op = Optional.ofNullable(null);
        if (op.isPresent()){
            op.get();
        }
    }

    @Test
    public void test4(){
        //orElse(T t):如果调用对象包含值,返回该值,否则返回 t
        Optional<Object> op = Optional.ofNullable(null);
        Employee emp = (Employee) op.orElse(new Employee("纸飞机", 23, 8000.88, Employee.Status.FREE));
        System.out.println(emp);
    }

    @Test
    public void test5(){
        //orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
        Optional<Object> op = Optional.ofNullable(null);
        Employee emp = (Employee) op.orElseGet(() -> new Employee("纸飞机", 23, 8000.88, Employee.Status.FREE));
        System.out.println(emp);
    }

    @Test
    public void test6(){
        //map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
        //flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional
        //将容器中的对象应用到map中的函数上
        Optional<Employee> op = Optional.ofNullable(new Employee("纸飞机", 23, 8000.88, Employee.Status.FREE));
        Optional<String> str = op.map(e -> e.getName());
        Optional<Double> sal = op.flatMap(e -> Optional.of(e.getSalary()));
        System.out.println(str.get());
        System.out.println(sal.get());
    }
}

应用场景测试

每一个人心中都有一个偶像,但这个偶像又可能没有的,值为null

public class People {

    private Idle idle;

    public People() {
    }

    public People(Idle idle) {
        this.idle = idle;
    }

    public Idle getIdle() {
        return idle;
    }

    public void setIdle(Idle idle) {
        this.idle = idle;
    }

    @Override
    public String toString() {
        return "People{" +
                "idle=" + idle +
                '}';
    }
}
public class Idle {

    private String name;

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Idle{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class TestOptional1 {

    @Test
    public void test(){
        People people = new People();
        String idleName = getIdleName(people);
        System.out.println(idleName);
    }

    //需求:获取一个人心中偶像的名字
    public String getIdleName(People people){
        return people.getIdle().getName();
    }
}
//发现问题:
//java.lang.NullPointerException

传统做法

//需求:获取一个人心中偶像的名字
    public String getIdleName(People people){
        if (people !=null){
            Idle idle = people.getIdle();
            if (idle!=null){
                String name = idle.getName();
                return name;
            }
        }
        return "大佬";
    }

使用Java 8 Optional容器类解决

    public String getIdleName1(Optional<People1> people1){
        return people1.orElse(new People1())
                        .getIdle()
                        .orElse(new Idle("大佬和大神"))
                        .getName();
    }
    @Test
    public void test(){
        People1 people1 = new People1();
        Optional<People1> op = Optional.ofNullable(null);
        String idleName = getIdleName1(op);
        System.out.println(idleName);
    }

九、接口中的默认方法和静态方法

1、默认方法

Java 8中接口中允许有方法体的默认方法(default修饰)。

但思考问题:

一个类同时继承了一个类且实现了一个接口,而继承的父类和接口中存在相同的方法时,在通过这个类的实例调这个相同的方法时,会冲突吗?会执行的是哪个方法?

测试

public interface MyFun {
    default String getName(){
        return "哈哈哈";
    }
}
public class MyClass {
    public String getName(){
        return "嘿嘿嘿";
    }
}
public class SubClass extends MyClass implements MyFun{

}
public class TestDefaultInterface {

    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        System.out.println(subClass.getName());
    }
}
//输出结果:
//嘿嘿嘿

接口中的默认方法遵循类优先原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时。

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
2、静态方法

Java 8 接口中允许有具体实现的静态方法。

public interface MyFun {

    static void getAddr(){
        System.out.println("addr");
    }

    static String Hello(){
        return "Hello World";
    }
}

十、新日期和时间

传统日期时间存在线程安全问题,即不是线程安全的。且比较不方便和混乱。

Java 8 的新日期和时间全部在java.time下这个包下,且不管怎么改变都会产生一个新的实例,即是线程安全的。

1、LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间日期和时间。

ISO-8601是国际标准化组织制定的现代公民的日期和时间的表示法。

@Test
    public void test(){
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);

        LocalDateTime ldt1 = LocalDateTime.of(2020, 10, 29, 8, 11, 10);
        System.out.println(ldt1);

        LocalDateTime ldt3 = ldt.plusYears(2);
        System.out.println(ldt3);

        LocalDateTime ldt4 = ldt.minusMonths(6);
        System.out.println(ldt4);

        System.out.println(ldt.getYear());
        System.out.println(ldt.getMonthValue());
        System.out.println(ldt.getDayOfMonth());
        System.out.println(ldt.getHour());
        System.out.println(ldt.getMinute());
        System.out.println(ldt.getSecond());
    }
//输出结果:
//2020-10-29T08:15:26.785
//2020-10-29T08:11:10
//2022-10-29T08:15:26.785
//2020-04-29T08:15:26.785
//2020
//10
//29
//8
//15
//26

2、Instant

Instant:时间戳(以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒数)

    @Test
    public void test1(){
        Instant ins = Instant.now();//默认是获取的UTC时区
        System.out.println(ins);

        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
        System.out.println(odt);

        System.out.println(ins.toEpochMilli());

        Instant ins1 = Instant.ofEpochSecond(60);
        System.out.println(ins1);
    }
//输出结果:
//2020-10-29T00:24:04.286Z
//2020-10-29T08:24:04.286+08:00
//1603931044286
//1970-01-01T00:01:00Z

3、Duration、Period

Duration:计算两个“时间”之间的间隔

Period:计算两个“日期”之间的间隔

    @Test
    public void test3(){
        Instant ins = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Instant ins1 = Instant.now();
        Duration duration = Duration.between(ins, ins1);
        System.out.println(duration.toMillis());
        System.out.println("=================================");

        LocalDateTime ldt = LocalDateTime.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LocalDateTime ldt1 = LocalDateTime.now();
        System.out.println(Duration.between(ldt, ldt1).toMillis());
    }
//输出结果:
//1006
//=================================
//1016

    @Test
    public void test4(){
        LocalDate ld = LocalDate.of(2018, 4, 6);
        LocalDate ld1 = LocalDate.now();
        Period period = Period.between(ld, ld1);
        System.out.println(period);
        System.out.println(period.getYears());
        System.out.println(period.getMonths());
        System.out.println(period.getDays());
    }

//输出结果:
//P2Y6M23D
//2
//6
//23

4、时间校正器

TemporalAdjuster:时间校正器。有时我们可能需要获取列入:将日期调整到“下个周日”等操作。

TemporalAdjustters:该类通过静态方法提供了 大量的常用TemporalAdjuster的实现。

 @Test
    public void test5(){
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);

        LocalDateTime ldt1 = ldt.withDayOfMonth(4);//指定具体为哪一天(号)
        System.out.println(ldt1);

        LocalDateTime ldt2 = ldt.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
        System.out.println(ldt2);

        //自定义时间校正器
        LocalDateTime ldt4 = ldt.with(l -> {
            LocalDateTime ldt3 = (LocalDateTime) l;
            DayOfWeek day = ldt3.getDayOfWeek();
            if (day.equals(DayOfWeek.FRIDAY)) {
                return ldt3.plusDays(3);
            } else if (day.equals(DayOfWeek.SATURDAY)) {
                return ldt3.plusDays(2);
            } else {
                return ldt3.plusDays(1);
            }
        });
        System.out.println(ldt4);
    }
//输出结果:
//2020-10-29T09:01:43.490
//2020-10-04T09:01:43.490
//2020-10-31T09:01:43.490
//2020-10-30T09:01:43.490

5、格式化日期/时间

    @Test
    public void test6(){
        DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
        LocalDateTime ldt = LocalDateTime.now();
        String strDate = ldt.format(dtf);
        System.out.println(strDate);

        System.out.println("=======================");
        DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
        String strDateTime = ldt.format(dtf1);
        System.out.println(strDateTime);

        LocalDateTime ldt1 = ldt.parse(strDateTime,dtf1);
        System.out.println(ldt1);
    }
//输出结果:
//2020-10-29
//=======================
//2020年10月29日 09:10:37
//2020-10-29T09:10:37

6、时区ZonedDate、ZonedTime、ZonedDateTime

    @Test
    public void test7(){
        Set<String> set = ZoneId.getAvailableZoneIds();
        set.forEach(System.out::println);
    }

    @Test
    public void test8(){
        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
        System.out.println(ldt);

        LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        ZonedDateTime zdt = ldt1.atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println(zdt);//与UTC相比,有8小时时差
    }
//test8()输出结果:
//2020-10-29T03:21:40.219
//2020-10-29T09:21:40.235+08:00[Asia/Shanghai]

十一、重复注解和类型注解

Java8 对注解处理提供了两点改进:可重复注解以及可用于类型的注解

@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "纸飞机";
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {

    MyAnnotation[] value();
}
/**
 * 重复注解和类型注解
 */
public class TestAnnotation {

    @MyAnnotation("Hello")
    @MyAnnotation("World")
    public void show(){

    }

    public void show2(@MyAnnotation String str){

    }

    /**
     * 配合反射使用
     */
    @Test
    public void test() throws Exception {
        Class<TestAnnotation> clazz = TestAnnotation.class;
        Method method = clazz.getMethod("show");
        MyAnnotation[] mas = method.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation myAnnotation : mas) {
            System.out.println(myAnnotation.value());
        }
    }
}
//输出结果:
//Hello
//World

纸飞机78
18 声望4 粉丝

引用和评论

0 条评论