【Java8】Java8新特性

一、闲话

Java8已经出来很久了,今天闲来无事,将它整理一下,方便后面工作中查阅

Java8有着很多优点

  • 它的速度更快
  • 因为lambda表达式的出现,使得代码更少
  • 便于并行
  • 强大的Stream API
  • Optional类可以最大程度减少空指针

二、Lambda表达式

1、组成部分

主要组成部分:

  • Lambda形参列表(其实就是接口中抽象方法的形参列表)
  • ->操作符
  • Lambda方法体(其实就是重写的抽象方法的方法体)

2、Lambda表达式的语法

  • 无参数,无返回值
  • 有一个参数,无返回值
  • Lambda表达式的参数列表的数据类型可以省略,因为JVM编译器可以推断初数据类型,即类型判断
  • 只有一个参数时,左侧参数的括号可省略
  • 有两个以上的参数,有返回值,并且Lambda函数体中有多条语句
  • 如果方法体中只有一条执行语句,那么右侧的大括号和return都可以省略
package com.decade.java8.lambda;

import org.junit.Test;

import java.util.Comparator;
import java.util.function.Consumer;

public class Lambda1 {

    @Test
    public void test1() {
        // 语法一:无参无返回值,可以写成
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("execute r1");
            }
        };
        r1.run();
        System.out.println("==============================");

        Runnable r2 = () -> {
            System.out.println("execute r2");
        };
        r2.run();
    }

    @Test
    public void test2() {
        // 语法二:有一个参数,无返回值,可以写成
        Consumer<String> c1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        c1.accept("execute c1");
        System.out.println("===============================");

        Consumer<String> c2 = (String input) -> {
            System.out.println(input);
        };
        c2.accept("execute c2");
    }

    @Test
    public void test3() {
        // 语法三:lambda表达式的参数列表的数据类型可以省略,因为JVM编译器可以推断出数据类型,即类型判断
        Consumer<String> c3 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        c3.accept("execute c3");
        System.out.println("===============================");

        Consumer<String> c4 = (input) -> {
            System.out.println(input);
        };
        c4.accept("execute c4");
    }

    @Test
    public void test4() {
        // 语法四:只有一个参数时,左侧参数的括号可省略,可以写成
        Consumer<String> c5 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        c5.accept("execute c5");
        System.out.println("===============================");

        Consumer<String> c6 = input -> {
            System.out.println(input);
        };
        c6.accept("execute c6");
    }

    @Test
    public void test5() {
        // 有两个以上的参数,有返回值,并且lambda函数体中有多条语句时,可以写成
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };

        System.out.println(comparator.compare(8, 10));
        System.out.println("================================");

        Comparator<Integer> comparator2 = (o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(comparator2.compare(8, 10));
    }

    @Test
    public void test6() {
        // 语法六:如果方法体中只有一条执行语句,那么右侧的大括号和return都可以省略
        Comparator<Integer> comparator3 = (o1, o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(comparator3.compare(10, 8));
        System.out.println("===================================");
        Comparator<Integer> comparator4 = (o1, o2) ->  o1.compareTo(o2);
        System.out.println(comparator4.compare(10, 8));
    }
}

3、Lambda表达式的本质

作为函数式接口的实例,以前用匿名实现类表示的现在都可以使用Lambda表达式来写

4、函数式接口

1)定义
使用@FunctionalInterface修饰, 有且仅有一个 抽象方法的接口就称为函数式接口

可以使用@FunctionalInterface检查是否是函数式接口
假设接口中有不止一个抽象方法,那么该注解就会爆红

// 函数式接口
@FunctionalInterface
public interface Intfunction{
	T apply(int a);
}

// 调用函数式接口的类:
public class Test{
	public static Object test(int a, IntFunction intFunction){
		return intFunction.apply(i);
	}
	
	public static void main(String args[]){
		Test.test(1, (int 1)->{
			return 1;
		});
	}
}

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

  • Consumer(消费型接口):接受一个参数,无返回结果
  • Supplier(供给型接口):无参数,返回一个结果
  • Function(函数型接口):传一个参数,返回一个结果
  • Pridicate(断定型接口):接受一个参数,返回一个boolean值
package com.decade.java8.lambda;

import org.junit.Test;

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

/**
 * Java内置四大函数式接口
 *
 * 消费型接口 Consumer<T>     void accept(T t):传入一个类型为T的参数,不返回结果
 * 供给型接口 Supplier<T>     T get():不传入参数,返回一个类型为T的结果
 * 函数型接口 Function<T,R>   R apply(T t):传入一个类型为T的参数,返回一个类型为R的结果
 * 断定型接口 Predicate<T>    boolean test(T t):传入一个类型为T的参数,判断其是否满足某个约束,返回boolean值
 */
public class Lambda2 {

    @Test
    public void test1() {
        buyGoods(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("我在超市买了点东西,花了" + aDouble);
            }
        });
        System.out.println("=====================================");

        // 使用lambda表达式
        buyGoods(400, r -> System.out.println("我在超市买了点东西,花了" + r));

    }

    public void buyGoods(double money, Consumer<Double> consumer) {
        consumer.accept(money);
    }

    @Test
    public void test2() {
        List<String> stringList = Arrays.asList("北京","南京","纽约","巴黎");
        final List<String> result = filterString(stringList, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(result);
        System.out.println("=====================================");

        // 使用lambda表达式
        List<String> result2 = filterString(stringList, s -> s.contains("京"));
        System.out.println(result2);
    }

    // 根据给定约束过滤字符串,此规则由Predicate的test方法决定
    public List<String> filterString(List<String> strings, Predicate<String> predicate) {
        List<String> list = new ArrayList<>();

        // 如果满足predicate中的test方法,那么就将当前元素加入结果集
        for(String s : strings) {
            if (predicate.test(s)) {
                list.add(s);
            }
        }
        return list;
    }
}

3)方法引用与构造器引用

  • 方法引用
    • 使用场景:当我们要写在Lambda方法体中的操作,已经有方法去实现了,我们就可以直接使用方法引用
    • 方法引用本质上就是lambda表达式,它的使用格式为类(或者实例) :: 方法名,具体分为三种情况
      • 对象::非静态方法
      • 类::静态方法
      • 类::非静态方法
    • 使用要求:接口中的抽象方法的形参列表与返回值类型==方法引用的形参列表与返回值类型(针对情况1和情况2)
package com.decade.java8.lambda;

import org.junit.Test;

import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class Lambda3 {

    // 情况一:对象::实例方法
    @Test
    public void test1() {
        // Consumer中的     void accept(T t)
        // PrintStream中的  void println(T t)
        Consumer<String> consumer = str -> System.out.println(str);
        consumer.accept("test1北京");

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

        final PrintStream printStream = System.out;
        final Consumer<String> consumer1 = printStream::println;
        consumer1.accept("test1上海");

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

        // Employee中的String getName()
        // Supplier中的T get()
        final Employee employee = new Employee("1", "test");

        Supplier<String> supplier = () -> employee.getName();
        System.out.println("test1" + supplier.get());

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

        Supplier<String> supplier1 = employee::getName;
        System.out.println("test1" + supplier1.get());
    }

    class Employee {
        private String id;

        private String name;

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

        public String getId() {
            return id;
        }

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

        public String getName() {
            return name;
        }

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


    // 情况二:类::静态方法
    @Test
    public void test2() {
        // Comparator中的  Integer compare(T t1, T t2)
        // Integer中的     Integer compare(T t1, T t2)
        Comparator<Integer> comparator = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(comparator.compare(12, 23));

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

        Comparator<Integer> comparator1 = Integer::compareTo;
        System.out.println(comparator1.compare(23, 12));

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

        // Math中的取整方法  Long round(Double d)
        // Function中的    R    apply(T t)
        Function<Double, Long> function = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };

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

        Function<Double, Long> function1 = d -> Math.round(d);
        System.out.println(function1.apply(12.3));

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

        Function<Double, Long> function2 = Math::round;
        System.out.println(function2.apply(12.6));
    }

    // 情况三:类::非静态方法
    @Test
    public void test3() {
        // Comparator中的int compare(T t1, T t2)
        // String中的 int t1.compareTo(t2)
        Comparator<String> comparator = (t1, t2) -> t1.compareTo(t2);
        System.out.println(comparator.compare("abc", "abd"));

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

        Comparator<String> comparator2 = String::compareTo;
        System.out.println(comparator2.compare("abf", "aba"));

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

        final Employee employee = new Employee("002", "test2");

        Function<Employee, String> function = e -> e.getName();
        System.out.println(function.apply(employee));

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

        Function<Employee, String> function2 = Employee::getName;
        System.out.println(function2.apply(employee));
    }
}
  • 构造器引用、数组引用
    • 构造器引用
      • 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致
      • 抽象方法的返回值的类型即为构造方法所属的类
    • 数组引用:可以把数组看做一个特殊的类,写法与构造器引用一致
package com.decade.java8.lambda;

import org.junit.Test;

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

public class Lambda4 {

    @Test
    public void  test1() {
        Supplier<Student> supplier = new Supplier<Student>() {
            @Override
            public Student get() {
                return new Student();
            }
        };
        System.out.println("====================");
        Supplier<Student> supplier1 = () -> new Student();
        supplier1.get();

        System.out.println("====================");
        Supplier<Student> supplier2 = Student::new;
        supplier2.get();

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

        // Function中的  R apply(T t)
        Function<String, Student> function = id -> new Student(id);
        final Student apply = function.apply("001");
        System.out.println(apply);

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

        Function<String, Student> function1 = Student::new;
        final Student apply1 = function1.apply("002");
        System.out.println(apply1);

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

        // BiFunction中的  R apply(T t,U u)
        BiFunction<String, String, Student> biFunction = (id, name) -> new Student(id, name);
        final Student student = biFunction.apply("001", "test1");
        System.out.println(student);

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

        BiFunction<String, String, Student> biFunction1 = Student::new;
        final Student student1 = biFunction1.apply("002", "test2");
        System.out.println(student1);
    }

    class Student {
        private String id;
        private String name;

        public Student() {
            System.out.println("无参构造");
        }

        public Student(String id) {
            this.id = id;
            System.out.println("id相关的构造器方法");
        }

        public Student(String id, String name) {
            this.id = id;
            this.name = name;
            System.out.println("包含所有字段的有参构造");
        }

        public String getId() {
            return id;
        }

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

        public String getName() {
            return name;
        }

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

        @Override
        public String toString() {
            return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
        }
    }

    @Test
    public void test2() {
        Function<Integer, String[]> function = length -> new String[length];
        final String[] strings = function.apply(5);
        System.out.println(strings.length);

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

        Function<Integer, String[]> function1 = String[]::new;
        final String[] strings1 = function1.apply(15);
        System.out.println(strings1.length);
    }
}

三、Stream API

1、什么是Stream
它是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
集合关注的是存储数据,Stream关注的是对数据进行计算

2、注意点

  • Stream不会存储元素
  • Stream不会改变源对象,它会返回一个持有结果的新Stream对象
  • Stream的操作是延迟执行的,它会等到需要结果的时候才执行

3、Stream的三个操作步骤

  • 创建Stream:一个数据源(如集合、数组),获取一个Stream流
  • 中间操作:一个中间操作链,对数据进行处理,如过滤、映射等
  • 终止操作:一旦执行终止操作,就执行中间操作对数据进行处理,结果产生后,就不能再回头重新执行中间操作了,不可再被使用

4、创建Stream流

package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream1 {

    // 创建Stream方式一:通过集合获取
    @Test
    public void testCreate1() {
        final List<String> stringList = getStringList();
        // default Stream<E> stream(),返回一个顺序流,数据的处理按照顺序进行
        final Stream<String> stream = stringList.stream();

        // default Stream<E> parallelStream():返回一个并行流,数据的处理是并行的,不一定按照顺序进行
        final Stream<String> stringStream = stringList.parallelStream();
    }

    // 创建Stream方式二:通过数组获取
    @Test
    public void testCreate2() {
        // 调用Arrays类的 static <T>Stream stream(T[] array)
        final IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5});
    }

    // 创建Stream方式三:通过Stream.of()
    @Test
    public void testCreate3() {
        final Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    }

    // 创建Stream方式四:创建无限流
    @Test
    public void testCreate4() {
        // 迭代操作,返回由函数f迭代应用于初始元素seed产生的无限有序流,产生由seed, f(seed), f(f(seed))等组成的流
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

        // 返回一个无限顺序无序流,其中每个元素都由提供的provider生成。这适用于生成恒定流、随机元素流等
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }

    public static List<String> getStringList() {
        List<String> resList = new ArrayList<>();
        resList.add("111");
        resList.add("222");
        resList.add("333");
        resList.add("444");

        return resList;
    }
}

5、Stream的中间操作

  • 筛选与切片
    • filter(Predicate p) :过滤给定规则的元素
    • limit(Integer n) :截取给定数量的元素
    • skip(Integer n) :顺序流,跳过前n个元素,若不足n个,则返回一个空流,与limit(n)互补
    • distinct() :根据元素的hashcode和equals方法来去重
package com.decade.java8.stream;

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

public class Employee {
    private String id;
    private String name;
    private int age;
    private Double salary;

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

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Double getSalary() {
        return salary;
    }

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

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

    public static List<Employee> getEmployeeList() {
        List<Employee> employeeList = new ArrayList<>();

        final Employee test1 = new Employee("001", "test1", 20, 5000.32);
        final Employee test2 = new Employee("002", "test2", 25, 9000.32);
        final Employee test3 = new Employee("003", "test3", 32, 19800.00);
        final Employee test4 = new Employee("004", "test4", 45, 33000.00);

        employeeList.add(test1);
        employeeList.add(test2);
        employeeList.add(test3);
        employeeList.add(test4);

        return employeeList;
    }
}


package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream2 {
    @Test
    public void testFilter() {
        final List<Employee> employeeList = Employee.getEmployeeList();

        final Stream<Employee> stream = employeeList.stream();
        // 查询员工中年龄大于30岁的,filter(Predicate p)   过滤给定规则的元素
        stream.filter(e -> e.getAge() > 30).forEach(System.out::println);
    }

    @Test
    public void testLimit() {
        final List<Employee> employeeList = Employee.getEmployeeList();

        final Stream<Employee> stream = employeeList.stream();
        // 截取数据源中前三条数据,limit(Integer n) ===> 截取给定数量的元素
        stream.limit(3).forEach(System.out::println);
    }

    @Test
    public void testSkip() {
        final List<Employee> employeeList = Employee.getEmployeeList();

        final Stream<Employee> stream = employeeList.stream();
        // 跳过数据源中前三条数据,skip(Integer n) ===> 顺序流,跳过前n个元素,若不足n个,则返回一个空流,与limit(n)互补
        stream.skip(3).forEach(System.out::println);
    }

    @Test
    public void testDistinct() {
        final List<Employee> employeeList = Employee.getEmployeeList();

        final Employee test5 = new Employee("005", "test5", 30, 17458.00);
        employeeList.add(test5);
        employeeList.add(test5);

        final Stream<Employee> stream = employeeList.stream();
        // 这里会打印2次005员工
        employeeList.forEach(System.out::println);
        System.out.println("===============");

        // distinct() ===>根据元素的hashcode和equals方法来去重
        stream.distinct().forEach(System.out::println);
    }
}
  • 映射
    • map(Function f) :将流中的元素按照某种规则映射出来,一个元素类型为 T 的流转换成元素类型为 R 的流
    • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。map对比集合中的list.add(),flatMap对比集合中的list.addAll()去理解
package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream3 {
    @Test
    public void testMap() {
        // map(Function f) ===> 将流中的元素按照某种规则映射出来,一个元素类型为 T 的流转换成元素类型为 R 的流,此处就是将所有的值都大写
        List<String> list = Arrays.asList("aa","bb","cc","dd");
        list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

        System.out.println("================");
        // 获取年龄大于25岁的员工的年纪
        final List<Employee> employeeList = Employee.getEmployeeList();
        final Stream<Integer> ageStream = employeeList.stream().map(Employee::getAge);
        ageStream.filter(r -> r > 25).forEach(System.out::println);
    }

    @Test
    public void testFlatMap() {
        // flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
        List<String> list = Arrays.asList("aa","bb","cc","dd");
        Stream<Character> characterStream = list.stream().flatMap(Stream3::fromStringToStream);
        //输出a a b b c c d d
        characterStream.forEach(System.out::println);
    }

    // 将字符串中的多个字符进行拆分并转换成Stream,假设入参是aa会返回a,a
    public static Stream<Character> fromStringToStream(String str){
        ArrayList<Character> list = new ArrayList<>();
        for(Character c : str.toCharArray()){
            list.add(c);
        }
        return list.stream();
    }

    @Test
    public void testListAddAndAddAll() {
        ArrayList list = new ArrayList();
        list.add(1);
        list.add(2);

        ArrayList list2 = new ArrayList();
        list2.add(3);
        list2.add(4);

        ArrayList list3 = new ArrayList();
        list3.add(5);
        list3.add(6);

        list2.add(list);
        System.out.println(list2);
        System.out.println("==========");
        list3.addAll(list);
        System.out.println(list3);
    }
}
  • 排序
  • sorted():自然排序
  • sorted(Comparator com):定制排序,按照自己制定的规则进行排序
package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream4 {
    @Test
    public void test() {
        List<Employee> employeeList = Employee.getEmployeeList();

        List<Integer> list = Arrays.asList(1,4,2,8,7);
        //sorted()--->自然排序,输出1,2,3,7,8
        list.stream().sorted().forEach(System.out::println);
        //sorted(Comparator com)--->定制排序,此处会抛异常,因为Employee没有实现comparable接口
        // employeeList.stream().sorted().forEach(System.out::println);

        // 按照年龄进行排序
        employeeList.stream().sorted((e1, e2) -> {
            //按年龄从小到大输出,年龄一样按工资从低到高(想从大到小,在Double前面加一个-即可)
            int ageValue =  Integer.compare(e1.getAge(),e2.getAge());
            if(ageValue != 0){
                return ageValue;
            }else{
                return Double.compare(e1.getSalary(),e2.getSalary());
            }
        }).forEach(System.out::println);
    }
}

6、Stream的终止操作

  • 匹配与查找
package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream5 {
    @Test
    public void testMatch() {
        List<Employee> employeeList = Employee.getEmployeeList();

        //allMatch---检查是否匹配所有元素
        boolean allMatch = employeeList.stream().allMatch(e -> e.getAge() > 18);//是否所有人年纪都大于18
        System.out.println("是否所有人年纪都大于18:" + allMatch);

        //anyMatch---检查是否至少匹配一个元素
        boolean anyMatch = employeeList.stream().anyMatch(e -> e.getSalary() > 10000);//是否有一个人的薪资大于10000
        System.out.println("是否有一个人的薪资大于10000:" + anyMatch);

        //noneMatch---检查是否没有匹配元素
        boolean noneMatch = employeeList.stream().noneMatch(e -> e.getName().startsWith("陈"));//检查是否没有人姓陈
        System.out.println("检查是否没有人姓陈:" + noneMatch);

        //findFirst--返回第一个元素
        Optional<Employee> firstEmployee = employeeList.stream().findFirst();
        System.out.println("返回第一个元素:" + firstEmployee);

        // findAny--返回当前流中任意元素
        // 此执行在一般情况下,并不会随机输出,而是按顺序输出,因为该流是有顺序的串行流,按照最优执行会按照顺序,但是换成并行流之后也是如此
        Optional<Employee> anyEmployee = employeeList.stream().findAny();
        System.out.println("返回任意元素:" + anyEmployee);

        //count()----返回流中元素的总个数
        long count = employeeList.stream().filter(e -> e.getSalary() > 5000).count();//返回流中工资大于5000的总数
        System.out.println("返回流中工资大于5000的总数:" + count);

        //max(Comparator c)-----返回流中最大值
        Stream<Double> salaryStream = employeeList.stream().map(e -> e.getSalary());
        Optional<Double> maxSalary = salaryStream.max(Double :: compare);//返回最高工资
        System.out.println("返回最高工资:" + maxSalary);

        //min(Comparator c)----返回流中最小值
        Optional<Employee> minEmployee = employeeList.stream().min((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary()));//返回最低工资员工
        System.out.println("返回最低工资员工:" + minEmployee);

        //forEach(Comsumer c)---内部迭代
        employeeList.stream().forEach(System.out::println);
    }
}
  • 规约:规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作
package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream6 {
    @Test
    public void test() {
        //reduce(T identity,BinaryOperator)---可以将流中的元素反复结合起来与初始值做运算,得到一个值,返回T类型
        //练习:计算1-5的和
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        Integer sum = list.stream().reduce(0,Integer::sum);//初始值是0
        System.out.println(sum);

        //reduce(BinaryOperator)---可以将流中的元素反复结合起来,得到一个值,返回Optional<T>
        //练习:计算所有员工工资的总和
        List<Employee> employees = Employee.getEmployeeList();
        Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
        //也可以写成:Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
        Optional<Double> sumMoney = salaryStream.reduce((d1, d2) -> d1 + d2);
        System.out.println(sumMoney);
    }
}
  • 收集
    • collect(Collector c):Collector接口中的方法实现决定了如何对流执行收集的操作(如收集到List、Set、Map)
package com.decade.java8.stream;

import org.junit.Test;

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

public class Stream7 {
    @Test
    public void test() {
        //练习1:查找工工资大于6000的员工,将结果返回一个List或Set
        List<Employee> employees = Employee.getEmployeeList();
        List<Employee> employeesList = employees.stream().filter(e->e.getSalary() > 6000).collect(Collectors.toList());//把流中元素收集到List
        employeesList.forEach(System.out::println);

        System.out.println("====================");
        Set<Employee> employeesSet = employees.stream().filter(e->e.getSalary() > 6000).collect(Collectors.toSet());//把流中元素收集到Set
        employeesSet.forEach(System.out::println);
    }
}

四、Optional类(预防空指针)

1、概念
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测,很好的解决空指针异常

2、创建Optional类对象:of、ofNullable、empty

  • Optional.of(T t)、Optional.ofNullable(T t)都是拿Optional对象的value
  • Optional.of(T t)不能传空,会报空指针异常,Optional.ofNullable(T t)可以传空,它会返回一个空的Optional对象
  • Optional.empty()会返回一个空的Optional对象
package com.decade.java8.optional;

import org.junit.Test;

import java.util.Optional;

public class Optional1 {
    @Test
    public void test() {
        Optional<String> empOp = Optional.empty();
        System.out.println(empOp.orElse("aaa"));//输出结果是aaa

        Optional<String> ofOp = Optional.of("abc");
        System.out.println(ofOp.orElse("bbb"));//输出结果abc

        Optional<String> ofNullOpNull = Optional.ofNullable(null);
        System.out.println(ofNullOpNull.orElse("ccc"));//输出结果是ccc

        Optional<String> ofOpNull = Optional.of(null);
        System.out.println(ofOpNull.orElse("ddd"));//输出结果java.lang.NullPointerException
    }
}

运行结果如下
在这里插入图片描述
3、获取Optional容器的对象

  • get(T t):获取Optional中的value值,尽量不使用,因为要防止它为null的情况下抛出异常
  • orElse(T other):获取Optional中的value值,没有值,返回一个other
  • orElseGet(Supplier<? extends T> other):获取Optional中的value值,没有值则执行other操作并返回
  • orElseThrow(Supplier<? extends T> exceptionSupplier):获取Optional中的value值,没有值则执行exceptionSupplier操作并抛出异常
package com.decade.java8.optional;

import com.decade.java8.stream.Employee;
import org.junit.Test;

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

public class Optional2 {
    // 打印切割符
    private static final String SPLIT_STR = "=================";

    @Test
    public void test() {
        final List<Employee> employeeList = Employee.getEmployeeList();

        final Optional<String> firstEmpName = employeeList.stream().findFirst().map(Employee::getName);
        System.out.println(firstEmpName.get());

        System.out.println(SPLIT_STR);
        final Optional<Object> empty = Optional.empty();
        //抛出异常,java.util.NoSuchElementException: No value present
        System.out.println(empty.get());
    }

    @Test
    public void test2() {
        Optional<String> str = Optional.empty();
        //输出aa
        System.out.println(str.orElse("aa"));
        System.out.println(SPLIT_STR);

        String b = str.orElseGet(() -> {
            System.out.println("bb");
            return "123";
        });
        //输出bb和123
        System.out.println(b);
        System.out.println(SPLIT_STR);

        //输出cc 与java.lang.RuntimeException: 抛出异常
        String result = str.orElseThrow(()->{
            System.out.println("cc");
            return new RuntimeException("抛出异常");
        });
        System.out.println(result);
    }
}

4、判断Optional容器中是否包含对象
ifPresent、ifpresentOrElse:不为空时执行,该方法和orElse对立,表示不为空时执行,没有返回值

  • void ifPresent(Consumer<? super T> consumer):当Optional不为空时,将他的值传递给consumer
  • Boolean isPresent(),当Optional不为空,返回true
package com.decade.java8.optional;

import com.decade.java8.stream.Employee;
import org.junit.Test;

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

public class Optional3 {
    // 打印切割符
    private static final String SPLIT_STR = "=================";

    @Test
    public void test() {
        Optional<String> str = Optional.empty();
        // 无输出值
        str.ifPresent(r->{
            System.out.println("不为空");
        });
        System.out.println(SPLIT_STR);

        final List<Employee> employeeList = Employee.getEmployeeList();
        final Optional<Employee> firstEmployee = employeeList.stream().findFirst();
        // 输出值:不为空-->Employee{id='001', name='test1', age=20, salary=5000.32}
        firstEmployee.ifPresent(r->{
            System.out.println("不为空-->" + r.toString());
        });

        // 输出:true
        boolean result = firstEmployee.isPresent();
        System.out.println(result);
    }
}

5、映射

  • map(映射)转换成另一个Optional,和Stream中使用类似
    Optional.map(Function<? super T,?extend U> mapper):返回映射值的Optional,只要这个Optional不为空且结果不为null,否则产生一个空Optional
package com.decade.java8.optional;

import com.decade.java8.stream.Employee;
import org.junit.Test;

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

public class Optional4 {
    // 打印切割符
    private static final String SPLIT_STR = "=================";

    @Test
    public void test() {
        Optional<String> aaa = Optional.empty().map(r->{
            return "aa";
        });
        // 输出结果:Optional对象为空
        System.out.println(aaa.orElse("Optional对象为空"));
        System.out.println(SPLIT_STR);

        final List<Employee> list = Employee.getEmployeeList();
        Optional<String> aaa1 = list.stream().findFirst().map(r->{
            return r.getName();
        });
        //输出结果:test1
        System.out.println(aaa1.orElse("对象为空"));
    }
}

6、过滤

  • filter:过滤Optional,和stream使用类似
    Optional.filter(Predicate<? super T> predicate):按照predicate规定的规则进行过滤,不满足规则的返回empty
package com.decade.java8.optional;

import com.decade.java8.stream.Employee;
import org.junit.Test;

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

public class Optional5 {
    // 打印切割符
    private static final String SPLIT_STR = "=================";

    @Test
    public void test() {
        final List<Employee> list = Employee.getEmployeeList();
        Optional<String> name = list.stream().map(Employee::getName).findFirst().filter(r->{
            return true;
        });
        //输出结果:test1
        System.out.println(name.orElse("对象为空"));
        System.out.println(SPLIT_STR);

        Optional<String> name1 = list.stream().map(Employee::getName).findFirst().filter(r->{
            return false;
        });
        //输出结果:对象为空
        System.out.println(name1.orElse("对象为空"));
    }
}

7、flatMap

  • flatMap:连接Optional,连续调用,如果想在调用oneOptional之后再调用twoOptional,就需要使用flatMap
    Optional.flatMap(Function<? super T, Optional<U>>mapper):类似于map,但是map中返回的值自动被Optoinal包装,而flatMap返回值保持不变,但是入参必须是Optional类型

假设我们先现在有一个School类,有一个字段属性为Option封装的Tearch对象
Teacher下面又有一个Optional封装的Student对象

package com.decade.java8.optional;

import org.junit.Test;

import java.util.Optional;

public class School {
    private String name;
    // 属性为Option封装的Teacher对象
    private Optional<Teacher> teacher;

    public School(String name, Optional<Teacher> teacher) {
        this.name = name;
        this.teacher = teacher;
    }

    public String getName() {
        return name;
    }

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

    public Optional<Teacher> getTeacher() {
        return teacher;
    }

    public void setTeacher(Optional<Teacher> teacher) {
        this.teacher = teacher;
    }

    class Student{
        private String name;
        private int age;

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

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

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

    class Teacher{
        private String name;
        private Optional<Student> student;

        public Teacher(String name, Optional<Student> student) {
            this.name = name;
            this.student = student;
        }

        public String getName() {
            return name;
        }

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

        public Optional<Student> getStudent() {
            return student;
        }

        public void setStudent(Optional<Student> student) {
            this.student = student;
        }
    }
}

如果我们现在想获取Student的name属性,使用连续map会报错
School::getTeacher会返回School实例的teacher属性,但是使用map(School::getTearch)时,会在结果外再加一层Optional,最终结果为Optional<Optional<Teacher>>,而不是我们所想的Optional<Teacher>,如图所示
在这里插入图片描述
而使用flatmap就不会报错

public static String getStudentNameTrue(School school){
    return Optional.ofNullable(school)
        .flatMap(School::getTeacher)
        .flatMap(Teacher::getStudent)
        .map(Student::getName)
        .orElse("false");
}

由此可得,如果某个类的属性是Optional包装过的类型,那么对他进行操作时就要使用flatMap方法,如果不是Optional包装的,例如Student的name属性,就直接使用map就可以了

五、新时间和日期API

1、为什么要引入新的时间API

因为旧版的时间API存在很多问题,比如:

  • 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
  • 设计很差:Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义
  • 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题

2、具体使用代码样例及输出

package com.decade.java8.time;

import org.junit.Test;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Set;

public class TestLocalDateTime {
    public static final String SPLIT_STR = "===============";

    @Test
    public void testLocalDateTime() {
        // 获取当前时间
        final LocalDate localDate = LocalDate.now();
        final LocalTime localTime = LocalTime.now();
        final LocalDateTime localDateTime = LocalDateTime.now();
        // 2023-06-11
        // 21:17:10.080
        // 2023-06-11T21:17:10.080
        System.out.println(localDate);
        System.out.println(localTime);
        System.out.println(localDateTime);

        System.out.println(SPLIT_STR);

        // 获取指定时间
        final LocalDateTime localDateTime1 = LocalDateTime.of(2023, 6, 11, 16, 48, 32);
        final LocalDate localDate1 = LocalDate.of(2023, 6, 11);
        final LocalTime localTime1 = LocalTime.of(16, 49, 33);
        // 2023-06-11T16:48:32
        // 2023-06-11
        // 16:49:33
        System.out.println(localDateTime1);
        System.out.println(localDate1);
        System.out.println(localTime1);

        System.out.println(SPLIT_STR);

        // 对时间进行操作,使用plus就是加操作,使用minus就是减操作
        // LocalDateTime可以对年月日时分秒进行加减,LocalDate只能加减年月日,LocalTime只能加减时分秒
        // 对时间进行操作后会返回一个新的实例,避免了线程不安全的问题
        // 2023-06-13T16:48:32
        // 2023-06-12
        // 13:49:33
        System.out.println(localDateTime1.plusDays(2));
        System.out.println(localDate1.plusDays(1));
        System.out.println(localTime1.minusHours(3));

        System.out.println(SPLIT_STR);

        // 单独获取年月日等信息,使用get可以查看详细方法
        // 2023
        // 11
        // 16
        System.out.println(localDate.getYear());
        System.out.println(localDateTime.getDayOfMonth());
        System.out.println(localTime1.getHour());
    }

    @Test
    public void testInstant() {
        // Instant:时间戳,表示从Unix元年1070年1月1日00:00:00到某个时刻为止的毫秒值,取的是UTC时区,北京是UTC+8时区
        final Instant instant = Instant.now();
        // 2023-06-11T13:16:39.227Z
        System.out.println(instant);

        // 带偏移量的时间戳
        final OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        // 2023-06-11T21:16:39.227+08:00
        System.out.println(offsetDateTime);

        // 打印时间戳, 1686489399227
        System.out.println(instant.toEpochMilli());

        // 对Unix元年时间戳进行运算,此处就是加上60秒
        final Instant instant1 = Instant.ofEpochSecond(60);
        // 1970-01-01T00:01:00Z
        System.out.println(instant1);
    }


    @Test
    public void testDurationAndPeriod() {
        final Instant instant = Instant.ofEpochSecond(60);
        final Instant instant1 = Instant.ofEpochSecond(89);

        // Duration:计算两个时间之间的间隔
        final Duration duration = Duration.between(instant, instant1);
        // 29
        System.out.println(duration.getSeconds());

        System.out.println(SPLIT_STR);

        final LocalDateTime now = LocalDateTime.now();
        final LocalDateTime plusTime = now.plusHours(3);
        final Duration duration1 = Duration.between(now, plusTime);
        // 180
        System.out.println(duration1.toMinutes());

        System.out.println(SPLIT_STR);

        // Period:计算两个日期之间的间隔
        final LocalDate localDate = LocalDate.now();
        final LocalDate localDate1 = LocalDate.of(2015, 7, 1);
        final Period period = Period.between(localDate1, localDate);
        // 7年
        // 11个月
        // 10天
        System.out.println(period.getYears() + "年");
        System.out.println(period.getMonths() + "个月");
        System.out.println(period.getDays() + "天");
    }

    @Test
    public void testTemporalAdjuster() {
        final LocalDateTime localDateTime = LocalDateTime.now();
        // 2023-06-11T21:15:23.973
        System.out.println(localDateTime);

        // 将当前日期替换成本月的第x天
        final LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(10);
        // 2023-06-10T21:15:23.973
        System.out.println(localDateTime1);

        // TemporalAdjuster:时间校正器,它是函数接口,在TemporalAdjusters 类中有很多预定义的实现
        // TemporalAdjuster 可以执行复杂的日期操作,例如,可以获得下一个星期日对于日期、当月的最后一天、下一年的第一天
        // TemporalAdjusters类有很多预定义的static方法返回TemporalAdjuster对象,此处为获取下个周六的日期
        final LocalDateTime dateTime = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
        // 2023-06-17T21:15:23.973
        System.out.println(dateTime);

        // 自定义校正器,假设我们想要获取下一个工作日是什么时候
        final LocalDateTime workDay = localDateTime.with(l -> {
            LocalDateTime localDateTime2 = (LocalDateTime) l;
            final DayOfWeek dayOfWeek = localDateTime2.getDayOfWeek();

            // 如果今天是周五,那下一个工作日就是周一,需要加三天
            if (DayOfWeek.FRIDAY.equals(dayOfWeek)) {
                return localDateTime2.plusDays(3);
            } else if (DayOfWeek.SATURDAY.equals(dayOfWeek)) {
                // 如果今天是周六,那下一个工作日就是周一,需要加两天
                return localDateTime2.plusDays(2);
            } else {
                // 其他时候工作日都只需要加一天
                return localDateTime2.plusDays(1);
            }
        });
        // 下一个工作日是2023-06-12T21:15:23.973
        System.out.println("下一个工作日是" + workDay);
    }

    @Test
    public void testDateTimeFormatter() {
        // DateTimeFormatter:格式化时间/日期
        // iso的日期时间格式化程序,它使用可用的偏移量和区域来格式化或解析日期时间,例如'2011-12-03T10:15:30'
        final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
        LocalDateTime localDateTime = LocalDateTime.now();

        final String formatRes = localDateTime.format(formatter);
        // 2023-06-11T21:23:21.956
        System.out.println(formatRes);

        // ISO日期格式化程序,在可用的情况下使用偏移量格式化或解析日期,例如'2011-12-03',只打印日期
        final DateTimeFormatter formatter1 = DateTimeFormatter.ISO_DATE;
        // 2023-06-11
        System.out.println(localDateTime.format(formatter1));

        // 自定义的日期格式
        final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
        final String format = localDateTime.format(dateTimeFormatter);
        // 2023年06月11日 21:27:27
        System.out.println(format);

        // 转换回localTimeDate
        final LocalDateTime parseRes = LocalDateTime.parse(format, dateTimeFormatter);
        // 2023-06-11T21:30:25
        System.out.println(parseRes);
    }

    @Test
    public void testZone() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        // availableZoneIds.forEach(System.out::println);

        System.out.println(SPLIT_STR);

        // 将当前之间转换成指定时区的时间
        final LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Europe/Guernsey"));
        // 2023-06-11T14:35:06.913
        System.out.println(localDateTime);

        final LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Europe/Guernsey"));
        // 在时间后面拼接上时区信息
        final ZonedDateTime zonedDateTime = localDateTime1.atZone(ZoneId.of("Europe/Guernsey"));
        // 2023-06-11T14:38:19.518+01:00[Europe/Guernsey]
        System.out.println(zonedDateTime);
    }
}

如有错误,欢迎指正!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值