精华内容
下载资源
问答
  • java8新特性
    千次阅读
    2022-03-25 16:14:23

    Java 8

    一、介绍

    1、生态

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

    2、新特性

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

    3、温故而知新

    • Hashmap 底层结构/原理 老话题不再阐述 …

    • 并发hashmap …

    • Java虚拟机 …

    • Java内存模型 …

    二、Lambda 表达式

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

    1、从匿名类到 Lambda 的转换

    演变过程:

    • 垃圾代码 --> 策略模式 --> 匿名内部类 --> Lambda表达式

    Employee 实体类:

    package com.yyff.java8;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Employee {
    
        private Integer id;
        private String name;
        private Integer age;
        private Double salary;
    
        public String show() {
            return "测试方法引用!";
        }
    }
    

    自定义函数式接口:

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

    过滤工资大于5000的实现类:

    public class FilterEmployeeForSalary implements MyPredicate<Employee> {
    
    	@Override
    	public boolean test(Employee t) {
    		return t.getSalary() >= 5000;
    	}
    
    }
    

    过滤出工资大于5000的员工实现类:

    public class FilterEmployeeForSalary implements MyPredicate<Employee> {
        /**
         * 过滤出工资大于5000的员工
         * @param employee
         * @return
         */
        @Override
        public boolean test(Employee employee) {
            return employee.getSalary() > 5000;
        }
    }
    

    测试类:

    public class TestLambda1 {
    	
    	//原来的匿名内部类
    	@Test
    	public void test1(){
    		Comparator<String> com = new Comparator<String>(){
    			@Override
    			public int compare(String o1, String o2) {
    				return Integer.compare(o1.length(), o2.length());
    			}
    		};
    		
    		TreeSet<String> ts = new TreeSet<>(com);
    		
    		TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>(){
    			@Override
    			public int compare(String o1, String o2) {
    				return Integer.compare(o1.length(), o2.length());
    			}
    			
    		});
    	}
    	
    	//现在的 Lambda 表达式
    	@Test
    	public void test2(){
    		Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());
    		TreeSet<String> ts = new TreeSet<>(com);
    	}
    	
    	List<Employee> emps = Arrays.asList(
    			new Employee(101, "张三", 18, 9999.99),
    			new Employee(102, "李四", 59, 6666.66),
    			new Employee(103, "王五", 28, 3333.33),
    			new Employee(104, "赵六", 8, 7777.77),
    			new Employee(105, "田七", 38, 5555.55)
    	);
    
    	//需求:获取公司中年龄小于 35 的员工信息
    	public List<Employee> filterEmployeeAge(List<Employee> emps){
    		List<Employee> list = new ArrayList<>();
    		
    		for (Employee emp : emps) {
    			if(emp.getAge() <= 35){
    				list.add(emp);
    			}
    		}
    		
    		return list;
    	}
    	
    	@Test
    	public void test3(){
    		List<Employee> list = filterEmployeeAge(emps);
    		
    		for (Employee employee : list) {
    			System.out.println(employee);
    		}
    	}
    	
    	//需求:获取公司中工资大于 5000 的员工信息
    	public List<Employee> filterEmployeeSalary(List<Employee> emps){
    		List<Employee> list = new ArrayList<>();
    		
    		for (Employee emp : emps) {
    			if(emp.getSalary() >= 5000){
    				list.add(emp);
    			}
    		}
    		
    		return list;
    	}
    	
    	//优化方式一:策略设计模式
    	public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){
    		List<Employee> list = new ArrayList<>();
    		
    		for (Employee employee : emps) {
    			if(mp.test(employee)){
    				list.add(employee);
    			}
    		}
    		
    		return list;
    	}
    	
    	@Test
    	public void test4(){
    		List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());
    		for (Employee employee : list) {
    			System.out.println(employee);
    		}
    		
    		System.out.println("------------------------------------------");
    		
    		List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());
    		for (Employee employee : list2) {
    			System.out.println(employee);
    		}
    	}
    	
    	//优化方式二:匿名内部类
    	@Test
    	public void test5(){
    		List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
    			@Override
    			public boolean test(Employee t) {
    				return t.getId() <= 103;
    			}
    		});
    		
    		for (Employee employee : list) {
    			System.out.println(employee);
    		}
    	}
    	
    	//优化方式三:Lambda 表达式
    	@Test
    	public void test6(){
    		List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
    		list.forEach(System.out::println);
    		
    		System.out.println("------------------------------------------");
    		
    		List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
    		list2.forEach(System.out::println);
    	}
    	
    	//优化方式四:Stream API
    	@Test
    	public void test7(){
    		emps.stream()
    			.filter((e) -> e.getAge() <= 35)
    			.forEach(System.out::println);
    		
    		System.out.println("----------------------------------------------");
    		
    		emps.stream()
    			.map(Employee::getName)
    			.limit(3)
    			.sorted()
    			.forEach(System.out::println);
    	}
    }
    

    2、Lambda 表达式语法

    Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:

    • 左侧:指定了 Lambda 表达式需要的所有参数
    • 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

    口诀:

    • 写死小括号,拷贝右箭头,落地大括号
    • 左右遇一括号省
    • 左侧推断类型省

    3、类型推断

    上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”

    4、几种语法格式

    1、无参数,无返回值

    语法格式:

    无参数,无返回值:() -> System.out.println(“Hello Lambda!”);

    例如 Runnable接口:

     public class Test02 {
         int num = 10; //jdk 1.7以前 必须final修饰
         
         @Test
         public void test01(){
             //匿名内部类
             new Runnable() {
                 @Override
                 public void run() {
                     //在局部类中引用同级局部变量
                     //只读
                     System.out.println("Hello World" + num);
                 }
             };
         }
     
         @Test
         public void test02(){
             //语法糖
             Runnable runnable = () -> {
                 System.out.println("Hello Lambda");
             };
         }
     }
    

    2、有一个参数,并且无返回值

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

    若只有一个参数,小括号可以省略不写 x -> System.out.println(x)

     @Test
     public void test03(){
         Consumer<String> consumer = (a) -> System.out.println(a);
         consumer.accept("我觉得还行!");
     }
    

    3、有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句

     @Test
     public void test04(){
         Comparator<Integer> comparator = (a, b) -> {
             System.out.println("比较接口");
             return Integer.compare(a, b);
         };
     }
    

    4、有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)

     @Test
     public void test04(){
         Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
     }
    

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

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

    6、Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”

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

    5、函数式接口

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

    • 定义一个函数式接口:
    @FunctionalInterface
    public interface MyFun {
        Integer count(Integer a, Integer b);
    }
    
    • 用一下:
    @Test
    public void test05(){
        MyFun myFun1 = (a, b) -> a + b;
        MyFun myFun2 = (a, b) -> a - b;
        MyFun myFun3 = (a, b) -> a * b;
        MyFun myFun4 = (a, b) -> a / b;
    }
    
    • 再用一下:
    public Integer operation(Integer a, Integer b, MyFun myFun){
        return myFun.count(a, b);
    }
    
    @Test
    public void test06(){
        Integer result = operation(1, 2, (x, y) -> x + y);
        System.out.println(result);
    }
    

    6、案例

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

    • 定义实体类
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee {
        
        private Integer id;
        private String name;
        private Integer age;
        private Double salary;
    }
    
    • 定义 List 传入数据
    List<Employee> emps = Arrays.asList(
        new Employee(101, "Z3", 19, 9999.99),
        new Employee(102, "L4", 20, 7777.77),
        new Employee(103, "W5", 35, 6666.66),
        new Employee(104, "Tom", 44, 1111.11),
        new Employee(105, "Jerry", 60, 4444.44)
    );
    
    • @Test
    @Test
    public void test01(){
        Collections.sort(employeesList,(e1,e2)->{
            if (e1.getAge().equals(e2.getAge())) {
                return e1.getName().compareTo(e2.getName());
            }else {
                return Integer.compare(e1.getAge(),e2.getAge());
            }
        });
    
        for (Employee employee : employeesList) {
            System.out.println(employee);
        }
    }
    

    案例二:声明函数式接口,接口中声明抽象方法,String getValue(String str); 声明类 TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;再将一个字符串的第二个和第四个索引位置进行截取字串

    @FunctionalInterface
    public interface MyFun02<T> {
        /**
         * 测试案例2
         * @param t
         * @return
         */
        T getValue(T t);
    }
    
    /**
     * 案例二:声明函数式接口,接口中声明抽象方法,String getValue(String str);
     * 声明类 TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;
     * 再将一个字符串的第二个和第四个索引位置进行截取字串
     */
    @Test
    public void test02() {
        String str = "helloworld";
        
        // s -> s.toUpperCase() 可以写成 String::toUpperCase
        String toUpperString = toUpperString(str, s -> s.toUpperCase());
        System.out.println("toUpperString = " + toUpperString);
    }
    
    public String toUpperString(String string, MyFun02<String> myFun02) {
        return myFun02.getValue(string);
    }
    

    案例三:声明一个带两个泛型的函数式接口,泛型类型为<T, R> T 为参数,R 为返回值;接口中声明对应的抽象方法;在 TestLambda 类中声明方法,使用接口作为参数,计算两个 Long 类型参数的和;在计算两个 Long 类型参数的乘积

    三、函数式接口

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

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

    1、消费型接口

    /**
     * Consumer<T> 消费型接口 :
     */
    @Test
    public void consumerTest() {
        happy(2999.99, m -> System.out.println("此次消费了:" + m + "元"));
    }
    
    public void happy(Double money, Consumer<Double> consumer) {
        consumer.accept(money);
    }
    

    2、提供型接口

    /**
     * Supplier<T> 供给型接口 :
     */
    @Test
    public void supplierTest() {
        Random random = new Random();
        List<Integer> numList = getNumList(10, () -> random.nextInt(100));
        numList.forEach(System.out::println);
    }
    
    /**
     * 需求:产生指定个数的整数,并放入集合中
     *
     * @param num
     * @param sup
     * @return
     */
    public List<Integer> getNumList(int num, Supplier<Integer> sup) {
        List<Integer> list = new ArrayList<>();
    
        for (int i = 0; i < num; i++) {
            Integer n = sup.get();
            list.add(n);
        }
    
        return list;
    }
    

    3、函数型接口

    /**
     * Function<T, R> 函数型接口
     */
    @Test
    public void functionTest() {
        // s -> s.trim() 可替换成 String::trim
        String newStr = strHandler("\t\t\t 威武   ", s -> s.trim());
        System.out.println(newStr);
    
        String subStr = strHandler("  威武呀", s -> s.substring(2, 5));
        System.out.println(subStr);
    }
    
    /**
     * 需求:用于处理字符串
     *
     * @param str
     * @param fun
     * @return
     */
    public String strHandler(String str, Function<String, String> fun) {
        return fun.apply(str);
    }
    

    4、断言型接口

    /**
     * Predicate<T> 断言型接口:
     */
    @Test
    public void predicateTest(){
        List<String> list = Arrays.asList("Hello", "yxj", "Lambda", "www", "ok");
        List<String> strings = filterStr(list, p -> p.length() > 3);
        strings.forEach(System.out::println);
    }
    
    /**
     * 需求:将满足条件的字符串,放入集合中
     *
     * @param list
     * @param pre
     * @return
     */
    public List<String> filterStr(List<String> list, Predicate<String> pre) {
        List<String> strList = new ArrayList<>();
    
        for (String str : list) {
            if (pre.test(str)) {
                strList.add(str);
            }
        }
    
        return strList;
    }
    

    5、其他接口

    20201207110028521

    四、引用

    1、方法引用

    若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用(可以将方法引用理解为 Lambda 表达式的另外一种表现形式)

    语法格式:

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

    注意:

    • 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
    • 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

    ① 对象的引用 :: 实例方法名

    /**
     * 对象::实例方法
     */
    @Test
    public void test01() {
        PrintStream printStream = System.out;
        Consumer<String> consumer = s -> printStream.println(s);
        consumer.accept("aaa");
    
        Consumer<String> consumer2 = printStream::println;
        consumer2.accept("bbb");
    }
    
    @Test
    public void test02(){
        Employee emp = new Employee(101, "张三", 18, 9999.99);
        Supplier<String> supplier = ()->emp.getName();
        System.out.println(supplier.get());
    
        Supplier<String> supplier2 = emp::getName;
        System.out.println(supplier2.get());
    }
    
    @Test
    public void test03(){
        BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
        Double apply = fun.apply(99.99, 199.99);
        System.out.println(apply);
    
        BiFunction<Double, Double, Double> fun2 = Math::max;
        Double apply1 = fun2.apply(88.88, 188.88);
        System.out.println(apply1);
    }
    

    **注意:**Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致

    ② 类名 :: 静态方法名

    /**
     * 类名 :: 静态方法名
     */
    @Test
    public void test02() {
        Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
        System.out.println(comparator.compare(1, 2));
    
        Comparator<Integer> comparator2 = Integer::compare;
        System.out.println(comparator2.compare(10, 20));
    }
    

    ③ 类名 :: 实例方法名

    /**
     * 类名 :: 实例方法名
     */
    @Test
    public void test05() {
        BiPredicate<String, String> bp = (x, y) -> x.equals(y);
        boolean test = bp.test("hello word", "hello future");
    
        BiPredicate<String, String> bp2 = String::equals;
        boolean test2 = bp2.test("hello word", "hello future");
    
        System.out.println("-----------------------------------------");
    
        Function<Employee, String> function = e -> e.show();
        String apply = function.apply(new Employee());
        System.out.println(apply);
    
        Function<Employee, String> function2 = Employee::show;
        String apply1 = function2.apply(new Employee());
        System.out.println(apply1);
    }
    

    **条件:**Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method

    2、构造器引用

    /**
     * 构造器引用
     */
    @Test
    public void test06() {
        Supplier<Employee> supplier = () -> new Employee();
        Employee employee = supplier.get();
        System.out.println(employee);
    
        Supplier<Employee> supplier1 = Employee::new;
        Employee employee1 = supplier1.get();
        System.out.println(employee1);
    }
    
    @Test
    public void test07(){
        Supplier<List> supplier = ()->new ArrayList<Integer>();
        List list = supplier.get();
        list.add(1);
        list.add(2);
        System.out.println(list);
    
        Supplier<List> supplier1 = ArrayList::new;
        List list1 = supplier1.get();
        System.out.println(list1);
    }
    

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

    3、数组引用

    /**
     * 数组引用
     */
    @Test
    public void test08(){
        Function<Integer, String[]> fun = (args)->new String[args];
        String[] apply = fun.apply(10);
        System.out.println("apply.length = " + apply.length);
    
        System.out.println("--------------------------");
    
        Function<Integer, Employee[]> fun2 = Employee[]::new;
        Employee[] apply1 = fun2.apply(20);
        System.out.println("apply1.length = " + apply1.length);
    }
    
    apply.length = 10
    --------------------------
    apply1.length = 20
    
    Process finished with exit code 0
    

    4、Lambda 表达式中的闭包问题

    这个问题我们在匿名内部类中也会存在,如果我们把注释放开会报错,告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final,但是在编译期间虚拟机会帮我们加上 final 修饰关键字。

    import java.util.function.Consumer;
    public class Main {
        public static void main(String[] args) {
    
            int num = 10;
    
            Consumer<String> consumer = ele -> {
                System.out.println(num);
            };
    
            //num = num + 2;
            consumer.accept("hello");
        }
    }
    

    五、强大的 Stream API

    注意:产生一个全新的流,和原来的数据源没有关系(数据源不受影响)!!!

    1、什么是 Stream 流

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

    注意:

    Stream 自己不会存储元素

    Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream

    Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

    2、操作 Stream 流

    Stream 的操作三个步骤

    • 创建 Stream
      • 一个数据源(如:集合、数组),获取一个流
    • 中间操作
      • 一个中间操作链,对数据源的数据进行处理
    • 终止操作(终端操作)
      • 一个终止操作,执行中间操作链,并产生结果
    image-20220108170026154

    ① 创建 Stream 流

    Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

    • default Stream stream() : 返回一个顺序流(串行)
    • default Stream parallelStream() : 返回一个并行流
    /**
    * 创建流
    */
    @Test
    public void test01(){
        /**
        * 集合流
        *  - Collection.stream() 串行流
        *  - Collection.parallelStream() 并行流
        */
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
    
        //数组流
        //Arrays.stream(array)
        String[] strings = new String[10];
        Stream<String> stream2 = Arrays.stream(strings);
    
        //Stream 静态方法
        //Stream.of(...)
        Stream<Integer> stream3 = Stream.of(1, 2, 3);
    
        //无限流
        //迭代
        Stream<Integer> stream4 = Stream.iterate(0, (i) -> i+2);
        stream4.forEach(System.out::println);
    
        //生成
        Stream.generate(() -> Math.random())
            .limit(5)
            .forEach(System.out::println);
    }
    
    image-20220108171037080

    ② 中间操作

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

    a、筛选 / 切片
    方法描述
    filter接收 Lambda ,从流中排除某些元素
    limit截断流,使其元素不超过给定数量
    skip(n)跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
    distinct筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素

    filter 操作

       //2. 中间操作
       List<Employee> emps = Arrays.asList(
               new Employee(102, "李四", 59, 6666.66),
               new Employee(101, "张三", 18, 9999.99),
               new Employee(103, "王五", 28, 3333.33),
               new Employee(104, "赵六", 8, 7777.77),
               new Employee(104, "赵六", 8, 7777.77),
               new Employee(104, "赵六", 8, 7777.77),
               new Employee(105, "田七", 38, 5555.55)
       );
    
       //内部迭代:迭代操作 Stream API 内部完成
       @Test
       public void test2(){
           //所有的中间操作不会做任何的处理
           Stream<Employee> stream = emps.stream()
                   .filter((e) -> {
                       System.out.println("测试中间操作");
                       // 加 return 原因:因为lambda表达式中,如果有多条语句,需要用大括号括起来,最后一句为返回语句,要加return
                       return e.getAge() <= 35;
                   });
    
           //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
           stream.forEach(System.out::println);
       }
    
       //外部迭代
       @Test
       public void test3(){
           Iterator<Employee> it = emps.iterator();
    
           while(it.hasNext()){
               System.out.println(it.next());
           }
       }
    

    内部迭代:

    测试中间操作
    测试中间操作
    Employee{id=101, name='张三', age=18, salary=9999.99}
    测试中间操作
    Employee{id=103, name='王五', age=28, salary=3333.33}
    测试中间操作
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    测试中间操作
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    测试中间操作
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    测试中间操作
    
    Process finished with exit code 0
    

    limit 操作

    @Test
    public void test4(){
        emps.stream()
                .filter((e) -> {
                    System.out.println("短路!"); // &&  ||
                    return e.getSalary() >= 5000;
                }).limit(3)
                .forEach(System.out::println);
    }
    
    短路!
    Employee{id=102, name='李四', age=59, salary=6666.66}
    短路!
    Employee{id=101, name='张三', age=18, salary=9999.99}
    短路!
    短路!
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    
    Process finished with exit code 0
    

    skip(n) 操作

    @Test
    public void test5() {
        emps.parallelStream()
                .filter((e) -> e.getSalary() >= 5000)
                .skip(2)
                .forEach(System.out::println);
    }
    
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=105, name='田七', age=38, salary=5555.55}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    
    Process finished with exit code 0
    

    distinct 操作

    @Test
    public void test6() {
        emps.stream()
                .distinct()
                .forEach(System.out::println);
    }
    
    Employee{id=102, name='李四', age=59, salary=6666.66}
    Employee{id=101, name='张三', age=18, salary=9999.99}
    Employee{id=103, name='王五', age=28, salary=3333.33}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=105, name='田七', age=38, salary=5555.55}
    
    Process finished with exit code 0
    

    发现结果并没有去重,因为 distinct 操作是通过流所生成的 hashCode() 与 equals() 取除重复元素,所以要重新 hashCode() 和 equals() 方法

    重写 hashCode() 和 equals() 方法后

    Employee{id=102, name='李四', age=59, salary=6666.66}
    Employee{id=101, name='张三', age=18, salary=9999.99}
    Employee{id=103, name='王五', age=28, salary=3333.33}
    Employee{id=104, name='赵六', age=8, salary=7777.77}
    Employee{id=105, name='田七', age=38, salary=5555.55}
    
    Process finished with exit code 0
    
    b、映射
    方法描述
    map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
    mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
    mapTolnt(TolntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
    mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
    flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

    map:

    image-20220108232233968

    @Test
    public void test01() {
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        strList.stream().map(String::toUpperCase).forEach(System.out::println);
    
        System.out.println("-----------------");
    
        Stream<Stream<Character>> streamStream = strList.stream().map(StreamTest02::filterCharacter);
        streamStream.forEach(sm->sm.forEach(System.out::println));
    }
    
    /**
     * 把 String 类型字符串转换成 Character 类型的流
     * @param str
     * @return
     */
    public static Stream<Character> filterCharacter(String str) {
        List<Character> list = new ArrayList<>();
    
        for (char c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }
    
    AAA
    BBB
    CCC
    DDD
    EEE
    -----------------
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d
    e
    e
    e
    
    Process finished with exit code 0
    

    我们发现下面这种写法太麻烦了

    streamStream.forEach(sm->sm.forEach(System.out::println)); 
    

    所以我们可以用 flatMap(Function f)

    @Test
    public void test02(){
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        Stream<Character> stream = strList.stream().flatMap(StreamTest02::filterCharacter);
        stream.forEach(System.out::println);
    }
    
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d
    e
    e
    e
    
    Process finished with exit code 0
    

    那么 map() 和 flatMap() 有什么区别呢?

    • map():{{a,a,a},{b,b,b},{c,c,c}}
    • flatMap():{a,a,a,b,b,b,c,c,c}

    有点类似于集合中 add() 和 addAll() 方法的区别

    add():

    @Test
    public void test03(){
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
    
        List<Object> list = new ArrayList<>();
        list.add("111");
        list.add("222");
        list.add(strList);
        System.out.println(list);
    }
    
    [111, 222, [aaa, bbb, ccc, ddd, eee]]
    
    Process finished with exit code 0
    

    addAll():

    @Test
    public void test03(){
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
    
        List<Object> list = new ArrayList<>();
        list.add("111");
        list.add("222");
        ///对比使用 add 和 addAll
        // list.add(strList);
        list.addAll(strList);
        System.out.println(list);
    }
    
    [111, 222, aaa, bbb, ccc, ddd, eee]
    
    Process finished with exit code 0
    
    c、排序
    方法描述
    sorted()自然排序
    sorted(Comparator c)定制排序

    Comparable:自然排序

    @Test
    public void test04(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        list.stream()
            .sorted() //comparaTo()
            .forEach(System.out::println);
    }
    

    Comparator:定制排序

    @Test
    public void test05(){
        emps.stream()
            .sorted((e1, e2) -> { //compara()
                if (e1.getAge().equals(e2.getAge())){
                    return e1.getName().compareTo(e2.getName());
                } else {
                    return e1.getAge().compareTo(e2.getAge());
                }
            })
            .forEach(System.out::println);
    }
    

    ③ 终止操作

    a、查找 / 匹配
    方法描述
    allMatch检查是否匹配所有元素
    anyMatch检查是否至少匹配一个元素
    noneMatch检查是否没有匹配所有元素
    findFirst返回第一个元素
    findAny返回当前流中的任意元素
    count返回流中元素的总个数
    max返回流中最大值
    min返回流中最小值
    forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代它有你把送代做了)

    给 Employee 加一个状态

    public class Employee {
        private long id;
        private String name;
        private int age;
        private double salary;
        private status status;
        
        // 略
        
        public enum status {
            // 空闲
            FREE,
    
            // 忙碌
            BUSY,
    
            // 休假
            VOCATION
        }
    }
    

    测试:

    List<Employee> emps = Arrays.asList(
            new Employee(102, "李四", 59, 6666.66, Employee.status.FREE),
            new Employee(101, "张三", 18, 9999.99, Employee.status.BUSY),
            new Employee(103, "王五", 28, 3333.33, Employee.status.VOCATION),
            new Employee(104, "赵六", 8, 7777.77, Employee.status.FREE),
            new Employee(104, "赵六", 8, 7777.77, Employee.status.FREE),
            new Employee(104, "赵六", 8, 7777.77, Employee.status.FREE),
            new Employee(105, "田七", 38, 5555.55, Employee.status.BUSY)
    );
    
    @Test
    public void test04() {
        boolean b = emps.stream().allMatch(e -> e.getStatus().equals(Employee.status.BUSY));
        System.out.println(b);
    
        System.out.println("--------------");
    
        /// 可替换成 Comparator.comparingDouble(Employee::getSalary)
        Optional<Employee> first = emps.stream()
            .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
            .findFirst();
        Employee employee = first.get();
        System.out.println(employee);
    }
    
    false
    --------------
    Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}
    
    Process finished with exit code 0
    
    findAny:

    如果用串行流 stream 的话结果可能是一样的,但是并行流 parallelStream 结果就是随机的

    /**
     * 从所有员工中任意找出一个状态为 FREE 的员工
     */
    @Test
    public void test05(){
        Optional<Employee> any = emps.parallelStream().filter(e -> e.getStatus().equals(Employee.status.FREE)).findAny();
        System.out.println(any.get());
    }
    
    max/min:
    @Test
    public void test06(){
        /// 获取工资最高的员工信息
        System.out.println(emps.stream().max(Comparator.comparingDouble(Employee::getSalary)).get());
    
        System.out.println("----------");
    
        /// 获取员工的最高工资
        System.out.println(emps.stream()
                           .map(Employee::getSalary) /// 员工工资提取到 map 中
                           .max(Double::compare).get()); /// 从 map 中选出符合条件的
    }
    
    Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}
    ----------
    9999.99
    
    Process finished with exit code 0
    
    b、归约 / 收集

    归约

    方法描述
    reduce(T identity, BinaryOperator b)可以将流中的数据反复结合起来,得到一个值。返回 T
    reduce(BinaryOperator b)可以将流中的数据反复结合起来,得到一个值。返回 Optional

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

    @Test
    public void test07(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
        System.out.println(reduce);
    
        System.out.println("-----------");
    
        Optional<Double> optional = emps.stream().map(Employee::getSalary).reduce(Double::sum);
        System.out.println(optional.get());
    }
    
    55
    -----------
    33333.355
    
    Process finished with exit code 0
    

    reduce 运算逻辑:

    image-20220109165449851

    返回值类型不同:

    image-20220109170507868

    收集

    方法描述
    collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

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

    image-20220109174122062

    image-20220109174142199

    toList …:
    /**
     * 把员工的名字收集到指定的集合中
     */
    @Test
    public void test08(){
        List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList());
        list.forEach(System.out::println);
    
        System.out.println("--------------");
    
        Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet());
        set.forEach(System.out::println);
    
        System.out.println("--------------");
    
        Map<Long, String> collect = emps.stream().collect(Collectors.toMap(Employee::getId, Employee::getName));
        System.out.println(collect);
    }
    
    李四
    张三
    王五
    赵六
    田七
    --------------
    李四
    张三
    王五
    赵六
    田七
    --------------
    {101=张三, 102=李四, 103=王五, 104=赵六, 105=田七}
    
    Process finished with exit code 0
    
    总数、平均值…:
    @Test
    public void test09() {
        /// 总数
        // 等价于 Long count = emps.stream().count()
        // 还等价于 Long count = (long) emps.size(); 
        Long count = emps.stream().collect(Collectors.counting());
        System.out.println(count);
    
        //平均值
        Double avg = emps.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avg);
    
        /// 总和
        // 等价于 Double sum = emps.stream().mapToDouble(Employee::getSalary).sum();
        Double sum = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(sum);
    
        //最大值
        Optional<Employee> max = emps.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(max.get());
    
        //最小值
        Optional<Double> min = emps.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(min.get());
    }
    
    分组:
    /**
     * 分组
     */
    @Test
    public void test10() {
        Map<Employee.status, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
        collect.forEach((key, value) -> System.out.println(key + ":" + value));
    }
    
    VOCATION:[Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}]
    BUSY:[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}, Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]
    FREE:[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}, Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}]
    
    Process finished with exit code 0
    
    多级分组:
    /**
     * 多级分组
     */
    @Test
    public void test11() {
        Map<Employee.status, Map<Object, List<Employee>>> collect = emps.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
            if (e.getAge() < 35) {
                return "青年";
            } else if (e.getAge() < 50) {
                return "中年";
            }else {
                return "老年";
            }
        })));
        collect.forEach((key, value) -> System.out.println(key + ":" + value));
    }
    
    BUSY:{青年=[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}], 中年=[Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]}
    VOCATION:{青年=[Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}]}
    FREE:{青年=[Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}], 老年=[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}]}
    
    Process finished with exit code 0
    
    分区:
    /**
     * 分区
     */
    @Test
    public void test12(){
        Map<Boolean, List<Employee>> collect = emps.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
        collect.forEach((key, value) -> System.out.println(key + ":" + value));
    }
    
    false:[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}, Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}, Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}, Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]
    true:[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}]
    
    Process finished with exit code 0
    
    summing:
    /**
     * summing
     */
    @Test
    public void test13(){
        DoubleSummaryStatistics collect = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
        double max = collect.getMax();
        double min = collect.getMin();
        long count = collect.getCount();
        double average = collect.getAverage();
        double sum = collect.getSum();
    }
    
    joining:
    /**
     * joining
     */
    @Test
    public void test14(){
        String collect = emps.stream().map(Employee::getName).collect(Collectors.joining(",","start ==","== end"));
        System.out.println(collect);
    }
    
    start ==李四,张三,王五,赵六,田七== end
    
    Process finished with exit code 0
    

    3、Stream API 练习

    案例一:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?

    如:给定【1,2,3,4,5】,返回【1,4,9,16,25】

    /**
     * 案例一:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?
     * 如:给定【1,2,3,4,5】,返回【1,4,9,16,25】
     */
    @Test
    public void test01(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> stream = list.stream().map(e -> e * e);
        stream.forEach(System.out::println);
    
        System.out.println("-----------------------");
    
        Integer[] integer = new Integer[]{1, 2, 3, 4, 5};
        Arrays.stream(integer).map(e->e*e).forEach(System.out::println);
    }
    

    案例二:怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?

    /**
     * 案例二:怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?
     */
    @Test
    public void test02(){
        Optional<Integer> reduce = emps.stream().map(e -> 1).reduce(Integer::sum);
        System.out.println(reduce.get());
    }
    

    案例三:

    Trader 交易员类:

    public class Trader {
        private String name;
        private String city;
    }
    

    Transaction 交易类:

    public class Transaction {
        private Trader trader;
        private int year;
        private int value;
    }
    

    TransactionTest 测试类:

    public class TransactionTest {
        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年发生的所有交易, 并按交易额排序(从低到高)
    /**
     * 1. 找出2011年发生的所有交易, 并按交易额排序(从低到高)
     */
    @Test
    public void test01(){
        int targetYear = 2011;
        /// 可替换成 
        // Stream<Transaction> sorted = transactions.stream().filter(e -> e.getYear() == targetYear).sorted(Comparator.comparingInt(Transaction::getValue));
        Stream<Transaction> sorted = transactions.stream().filter(e -> e.getYear() == targetYear).sorted((e1, e2) -> {
            return Integer.compare(e1.getValue(), e2.getValue());
        });
        sorted.forEach(System.out::println);
    }
    
    Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}
    Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400}
    
    Process finished with exit code 0
    
    1. 交易员都在哪些不同的城市工作过?
    /**
     * 2. 交易员都在哪些不同的城市工作过?
     */
    @Test
    public void test02(){
        transactions.stream().map(e->e.getTrader().getCity()).distinct().forEach(System.out::println);
    }
    
    Cambridge
    Milan
    
    Process finished with exit code 0
    
    1. 查找所有来自剑桥的交易员,并按姓名排序
    /**
     * 3. 查找所有来自剑桥的交易员,并按姓名排序
     */
    @Test
    public void test03(){
        String targetCity = "Cambridge";
        transactions.stream()
                .filter(e->e.getTrader().getCity().equals(targetCity))
                .map(Transaction::getTrader)
                // 可替换成 .sorted(Comparator.comparing(Trader::getName))
                .sorted((t1,t2)->t1.getName().compareTo(t2.getName()))
                .distinct()
                .forEach(System.out::println);
    }
    
    Trader{name='Alan', city='Cambridge'}
    Trader{name='Brian', city='Cambridge'}
    Trader{name='Raoul', city='Cambridge'}
    
    Process finished with exit code 0
    
    1. 返回所有交易员的姓名字符串,按字母顺序排序
    /**
     * 4. 返回所有交易员的姓名字符串,按字母顺序排序
     */
    @Test
    public void test04(){
        System.out.println("=====按姓名字母顺序排=====");
        transactions.stream().map(e->e.getTrader().getName()).sorted().distinct().forEach(System.out::println);
    
        System.out.println("=====先排序后拼接=====");
        String reduce = transactions.stream().map(e -> e.getTrader().getName()).sorted().distinct().reduce("", String::concat);
        System.out.println(reduce);
    
        System.out.println("=====把名字拆成一个个字母然后按字母排序(忽略大小写)=====");
        transactions.stream()
                .map(e -> e.getTrader().getName())
                .flatMap(TransactionTest::filterCharacter)
                /// 可替换成 .sorted(String::compareToIgnoreCase)
                .sorted((e1,e2)->e1.compareToIgnoreCase(e2))
                .forEach(System.out::print);
    }
    
    /**
     * 把 String 字符串中的字符一个个提取出来转成一个个字符串,然后再转换成 Stream 流
     * @param str
     * @return
     */
    public static Stream<String> filterCharacter(String str){
        List<String> list = new ArrayList<>();
    
        for (Character c : str.toCharArray()) {
            list.add(c.toString());
        }
        return list.stream();
    }
    
    =====按姓名字母顺序排=====
    Alan
    Brian
    Mario
    Raoul
    =====先排序后拼接=====
    AlanBrianMarioRaoul
    =====把名字拆成一个个字母然后按字母排序(忽略大小写)=====
    aaaaaAaBiiilllMMnnoooorRRrruu
        
    Process finished with exit code 0
    
    1. 有没有交易员是在米兰工作的?
    /**
     * 5. 有没有交易员是在米兰工作的?
     */
    @Test
    public void test05(){
        String targetCity = "Milan";
        boolean b = transactions.stream().anyMatch(e -> e.getTrader().getCity().equals(targetCity));
        System.out.println(b);
    }
    
    true
    
    Process finished with exit code 0
    
    1. 打印生活在剑桥的交易员的所有交易额
    /**
     * 6. 打印生活在剑桥的交易员的所有交易额
     */
    @Test
    public void test06(){
        String targetCity = "Cambridge";
        Optional<Integer> sum = transactions.stream()
                .filter(e -> e.getTrader().getCity().equals(targetCity))
                .map(Transaction::getValue)
                .reduce(Integer::sum);
        System.out.println(sum.get());
    }
    
    2650
    
    Process finished with exit code 0
    
    1. 所有交易中,最高的交易额是多少

    注意审题:要的是最高交易额,而不是最高交易额的交易

    /**
     * 7. 所有交易中,最高的交易额是多少
     */
    @Test
    public void test07(){
        Optional<Integer> max = transactions.stream().map(Transaction::getValue).max(Integer::compare);
        System.out.println(max.get());
    
        System.out.println("=====注意审题:要的是最高交易额,而不是最高交易额的交易=====");
        System.out.println("=====以下的返回的是最高交易额的交易=====");
        Optional<Transaction> transaction = transactions.stream()
            .max(Comparator.comparingInt(Transaction::getValue));
        System.out.println(transaction.get());
    }
    
    1000
    =====注意审题:要的是最高交易额,而不是最高交易额的交易=====
    =====以下的返回的是最高交易额的交易=====
    Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2012, value=1000}
    
    Process finished with exit code 0
    
    1. 找到交易额最小的交易
    /**
     * 8. 找到交易额最小的交易
     */
    @Test
    public void test08(){
        Optional<Transaction> min = transactions.stream().min(Comparator.comparingInt(Transaction::getValue));
        System.out.println(min.get());
    }
    
    Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}
    
    Process finished with exit code 0
    

    六、并行流与串行流

    听不懂…略

    七、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

    Optional.of(T t):

    @Test
    public void test01(){
        Optional<Employee> employee = Optional.of(new Employee());
        System.out.println(employee.get());
    
        System.out.println("==========");
    
        Optional<Object> o = Optional.of(null);
        System.out.println(o.get());
    }
    
    Employee{id=0, name='null', age=0, salary=0.0, status=null}
    ==========
    
    java.lang.NullPointerException
    	at java.util.Objects.requireNonNull(Objects.java:203)
    	at java.util.Optional.<init>(Optional.java:96)
    	at java.util.Optional.of(Optional.java:108)
    	at lambda.OptionalTest.test01(OptionalTest.java:23)
    

    Optional.empty(T t):

    @Test
    public void test02(){
        Optional<Object> empty = Optional.empty();
        System.out.println(empty.get());
    }
    
    java.util.NoSuchElementException: No value present
    
    	at java.util.Optional.get(Optional.java:135)
    	at lambda.OptionalTest.test02(OptionalTest.java:30)
    

    Optional.ofNullable(T t):

    @Test
    public void test03(){
        Optional<Employee> employee = Optional.ofNullable(null);
        System.out.println(employee.get());
    }
    
    java.util.NoSuchElementException: No value present
    
    	at java.util.Optional.get(Optional.java:135)
    	at lambda.OptionalTest.test03(OptionalTest.java:36)
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Man {
        private Goddess goddess;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Goddess {
        private String name;
    }
    

    以前:

    @Test
    public void test04(){
        Man man = new Man();
        String goddessName = getGoddessName(man);
        System.out.println(goddessName);
    }
    
    /**
     * 获取一个男人心中的女神的名字
     * @param man
     * @return
     */
    public String getGoddessName(Man man){
        return man.getGoddess().getName();
    }
    
    java.lang.NullPointerException
    	at lambda.OptionalTest.getGoddessName(OptionalTest.java:53)
    	at lambda.OptionalTest.test04(OptionalTest.java:43)
    

    解决:

    @Test
    public void test04(){
        Man man = new Man();
        String goddessName = getGoddessName(man);
        System.out.println(goddessName);
    }
    
    /**
     * 获取一个男人心中的女神的名字
     * @param man
     * @return
     */
    public String getGoddessName(Man man){
        /// 判空,防止空指针
        // return man.getGoddess().getName();
        if (man != null){
            Goddess goddess = man.getGoddess();
            if (goddess != null){
                return goddess.getName();
            }
        }
        return "苍老师";
    }
    
    苍老师
    
    Process finished with exit code 0
    

    那如果一层套一层,男人心中有女神,女神心中有她的男神,男神又有他的女神…此时如果再用 if 判空就很麻烦了

    现在:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class NewMan {
        private Optional<Goddess> goddess = Optional.empty();
    }
    
    @Test
    public void test05(){
        Optional<Goddess> goddess = Optional.ofNullable(new Goddess("深田"));
        Optional<NewMan> newMan = Optional.ofNullable(new NewMan(goddess));
        String name1 = getGoddessName2(newMan);
        System.out.println(name1);
    
        System.out.println("==========");
    
        Optional<NewMan> optional = Optional.ofNullable(null);
        String name2 = getGoddessName2(optional);
        System.out.println(name2);
    }
    
    public String getGoddessName2(Optional<NewMan> newMan){
        return newMan.orElse(new NewMan())
                .getGoddess()
                .orElse(new Goddess("三上悠亚"))
                .getName();
    }
    
    深田
    ==========
    三上悠亚
    
    Process finished with exit code 0
    
    更多相关内容
  • 【15章Java 8 新特性】最全Java 8新特性 详解

    千次阅读 多人点赞 2022-02-14 12:10:21
    【15章Java 8 新特性:重复注解、类型注解、Stream API、函数式接口、Optional类、方法构造器数组引用、接口默认方法、的日期时间API、Lambda表达式、类型推断、编译工具、JDK更新】

    ❤写在前面
    ❤博客主页:努力的小鳴人
    ❤系列专栏:JavaSE超详总结😋
    ❤欢迎小伙伴们,点赞👍关注🔎收藏🍔一起学习!
    ❤如有错误的地方,还请小伙伴们指正!🌹

    🔥系列传送门JavaSE超详总结😋
    【14章Java反射机制】Java Reflection【热榜】
    【13章网络编程】鲜为人知网络编程【热榜】
    【12章 Java IO流】程序员必看 IO流 详解【热榜】
    【附章5计算机字符编码】多种字符编码集的说明【热榜】

    🍔彩蛋:
    🔎牛客网是一个兼具 面经、刷题、求职 的神器,是我们的最佳选择,我用过很不错呢~ 👉点击注册开始刷题
    小鸣人因为准备考研,学完C++准备浅练下算法,后续会推出牛客网刷题记录专栏,到时希望大家多多关注~


      Java 8 是 Java 语言开发的一个主要版本,oracle公司于2014年3月发布,是自Java 5 以来最具革命性的版本,Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性

    一、注解

    重复注解

    在Java 8中使用 @Repeatable 注解定义重复注解
    在某些情况下,我们希望将相同的注释应用于声明或类型使用,从 Java SE 8 版本开始,重复注释能够做到这一点。

    如:正在编写代码以使用计时器服务,该服务使您能够在给定时间或按特定时间表运行方法,类似于 UNIX cron服务。现在您想设置一个计时器来在每月的最后一天和每个星期五晚上 11:00运行方法doPeriodicCleanup@Schedule 。要设置计时器运行,请创建一个注释并将其两次应用于doPeriodicCleanup方法。第一次使用指定该月的最后一天,第二次指定周五晚上 11 点
    如以下代码示例所示:

    @Schedule(dayOfMonth="last")
    @Schedule(dayOfWeek="Fri", hour="23")
    公共无效 doPeriodicCleanup() { ... }
    

    类型注解

    允许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为Type Annotation(类型注解)
    Java 8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值
    ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中
    ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中

    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    @interface MyAnnotation {}
    

    二、Stream API

    Stream API 把真正的函数式编程风格引入到Java中

    1. Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
    2. 操作步骤:
      创建 Stream:一个数据源(如:集合、数组),获取一个流
      中间操作:一个中间操作链,对数据源的数据进行处理
      终止操作:一旦执行终止操作,就执行中间操作链并产生结果,之后不会再被使用
      在这里插入图片描述

    ①创建 Stream

    1. 创建 Stream方式一:通过集合
      提供了两个获取流方法:
    //返回一个顺序流
    default Stream<E> stream()
    //返回一个并行流
    default Stream<E> parallelStream()
    
    1. 创建 Stream方式二:通过数组
      Arrays 的静态方法 stream() 可以获取数组流:
    //返回一个流
    static <T> Stream<T> stream(T[] array)
    //重载形式,能够处理对应基本类型的数组:
    public static IntStream stream(int[] array)
    public static LongStream stream(long[] array)
    public static DoubleStream stream(double[] array)
    
    1. 创建 Stream方式三:通过Stream的of()
      调用Stream类静态方法 of(), 通过显示值创建一个流
    //返回一个流
    public static<T> Stream<T> of(T... values)
    
    1. 创建 Stream方式四:创建无限流
      使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流
    //迭代
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    //生成
    public static<T> Stream<T> generate(Supplier<T> s)
    

    创建无限流举例:

    public void test4() {
    	// 迭代
    		Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
    		stream.limit(10).forEach(System.out::println);
    	// 生成
    		Stream<Double> stream1 = Stream.generate(Math::random);
    		stream1.limit(10).forEach(System.out::println);
    }
    

    ②中间操作

    多个中间操作可以连接起来形成一个流水线

    1. 筛选与切片
      filter(Predicate p) 接收 Lambda , 从流中排除某些元素
      distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
      limit(long maxSize) 截断流,使其元素不超过给定数量
      skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流
    2. 映射
      在这里插入图片描述
    3. 排序
      sorted() 产生一个新流,其中按自然顺序排序
      sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

    ③终止操作

    终端操作会从流的流水线生成结果

    1. 匹配与查找
      在这里插入图片描述
    2. 归约
      在这里插入图片描述
    3. 收集
      collect(Collector c)
      将流转换为其他形式,接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

    🔥Collectors

    在这里插入图片描述

    三、函数式接口

    Functional

    只包含一个抽象方法的接口,称为函数式接口

    1. 可以在一个接口上使用 @FunctionalInterface 注解,可以检查它是否是一个函数式接口
    2. java.util.function 包下定义了Java 8 的丰富的函数式接口
    3. Java不但可以支持OOP,还可以支持OOF(面向函数编程)
    4. 在Java8中,Lambda表达式就是一个函数式接口的实例,这就是Lambda表达式和函数式接口的关系;也是说只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
    5. 举例
      在这里插入图片描述

    🔥自定义函数式接口

    @FunctionalInterface .
     public interface MyNumber{
     public double getValue();
     }
    //函数式接口中使用泛型:
    @FunctionalInterface 
     public interface MyFun<T>{
     public T getValue(T t);
     }
    

    作为参数传递 Lambda 表达式
    为了将 Lambda 表达式作为参数传递,接收Lambda表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    四、Optional类

    最大化减少空指针异常,解决空指针异常

    Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在

    1. Optional提供很多有用的方法,这样我们就不用显式进行空值检测
      创建Optional类对象的方法:
      Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
      Optional.empty() : 创建一个空的 Optional 实例
      Optional.ofNullable(T t):t可以为null
      判断Optional容器中是否包含对象:
      boolean isPresent() : 判断是否包含对象
      void ifPresent(Consumer<? super T> consumer) :如有值,就执行Consumer接口的实现代码且该值会作为参数传给它
      获取Optional容器的对象:
      T get(): 如果调用对象包含值,返回该值,否则抛异常
      T orElse(T other) 如果有值则将其返回,否则返回指定的other对象。
      T orElseGet(Supplier<? extends T> other) 如果有值则将其返回,否则返回由Supplier接口实现提供的对象
      T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常

    五、引用

    🔥方法引用

    1. 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
    2. 方法引用就是Lambda表达式,也就是函数式接口的一个实例
    3. 要求:
      实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
    4. 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来
    5. 三种主要使用情况:
      对象::实例方法名
      类::静态方法名
      类::实例方法名
    6. 举例
      在这里插入图片描述

    🔥构造器引用

    1. 格式: ClassName::new
    2. 要求:构造器参数列表与接口中抽象方法的参数列表一致且方法的返回值即为构造器对应类的对象
    3. 举例
      在这里插入图片描述

    🔥数组引用

    格式: type[] :: new
    在这里插入图片描述

    六、接口的默认方法

    扩展方法
    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字

    
    interface marla {
        double calculate(int a);
        default double sqrt(int a) {
            return Math.sqrt(a);
        }
    }
    
    marla marla = new marla() {
        @Override
        public double calculate(int a) {
            return sqrt(a * 100);
        }
    };
    marla.calculate(100);     // 100.0
    marla.sqrt(16);           // 4.0
    

    marla接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用

    七、新的时间日期API

    请看传送门 >>> 【07章Java常用类】盘点 Java常用类🚩

    八、Lambda表达式

    新的语法,代码更少
    Lambda 是匿名函数,Lambda 表达式可以理解为是一段可传递的代码,将代码像数据一样进行传递

    1. 语法
      引入一种新的语法元素和操作符 “->” ,该操作符被称为 Lambda 操作符或箭头操作
      将 Lambda 分为两个部分:
      左侧:指定了 Lambda 表达式需要的参数列表
      右侧:指定了 Lambda 体,是抽象方法的实现逻辑
    2. 语法格式

    语法格式一:无参,无返回值

    Runnable r = () -> {System.out.println("I am Lambda!");};
    

    语法格式二:Lambda 需要一个参数,但是没有返回值

    Consumer<String> con = (String str) -> {System.out.println(str);};
    

    语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

    Consumer<String> con = (str) -> {System.out.println(str);};
    

    语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略

    Consumer<String> con = str -> {System.out.println(str);};
    

    语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

    Comparator<Integer> com = (x,y) -> {System.out.println("实现函数式接口方法!");
    	Return Integer,compare(x,y);
    };
    

    语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

    Comparator<Integer> com = (x,y) -> Interger.compare(x,y);
    
    1. 代码举例
    Runnable r1 = new Runnable(){
    	@override
    	public void run(){
    		System.out.println("Hello Lambda!");
    	}
    }
    

    匿名类到 Lambda 后

    Runnable r = () -> {System.out.println("Hello Lambda!");};
    

    九、类型推断

    Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖上下文环境,是由编译器推断出来的,这就是所谓的 “类型推断
    在这里插入图片描述

    十、新编译工具

    对开发者最有用的工具

    Nashorn引擎:jjs

    jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行
    举例:
    一个 un.js 文件

    function f() { 
         return 1; 
    }; 
    
    print( f() + 3 );
    

    可以在命令行中执行这个命令:jjs un.js,控制台输出结果是:4

    官方文档传送门 >>> jjs官方文档🚩

    类依赖分析器:jdeps

    jdeps可以展示包层级和类层级的Java类依赖关系,以.class文件、目录或者jar文件为输入,然后把依赖关系输出到控制台

    官方文档传送门 >>> jdeps官方文档🚩

    十一、JDK 更新

    并发

    基于新增的lambda表达式和steam特性,Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操

    Arrays

    在这里插入图片描述

    IO/NIO的改进

    1. 在传统的IO输入输出中,如果我们从流中去读数据,而数据源中没有数据时,程序就会阻塞该线程,而NIO是非阻塞式IO,NIO采用了内存映射的方式具体通过Channel对象来体现,Channel通道是模拟的传统IO,可以通过它读取和写入数据。它不同之处在于提供了map()方法,可以直接将一块数据映射到内存
      标准访问:

    在这里插入图片描述
    内存映射:
    在这里插入图片描述

    1. NIO也是面向缓冲的
      Buffer是一个数组容器,发送到Channel的对象都必须先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中
      Buffer与Channel之间的交互
    2. NIO中使用单个线程来处理多个Channel,一个线程可以管理多个网络连接。具体的实现是Selector
      Selector选择器的工作原理首先向Selector中注册Channel,然后Selector就可以检测到一个或多个NIO通道,并且能够了解每个通道是否已经做好准备进行读写
      请添加图片描述

    🎁总结:这Java 8新特性很多,学起来也学不太明白,写写笔记到处请教学习
    👌 作者算是一名Java初学者,文章如有错误,欢迎评论私信指正,一起学习~~
    😊如果文章对小伙伴们来说有用的话,点赞👍关注🔎收藏🍔就是我的最大动力!
    🚩不积跬步,无以至千里书接下回,欢迎再见🌹

    🍔彩蛋:
    🔎牛客网是一个兼具 面经、刷题、求职 的神器,是我们的最佳选择,我用过很不错呢~ 👉点击注册开始刷题
    小鸣人因为准备考研,学完C++准备浅练下算法,后续会推出牛客网刷题记录专栏,到时希望大家多多关注~

    展开全文
  • Java8新特性全面

    千次阅读 多人点赞 2022-03-21 11:19:49
    Java8新特性 简介 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM...

    Java8新特性

    简介

    Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。

    Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

    • 速度更快

    • 代码更少(增加了新的语法:Lambda 表达式)

    • 强大的 Stream API

    • 便于并行

    • 最大化减少空指针异常:Optional

    • Nashorn引擎,允许在JVM上运行JS应用

    1.Nashorn,发音"nass-horn",是德国二战时一个坦克的命名,同时也是java8新一代的javascript引擎。

    2.javascript运行在jvm已经不是新鲜事了,Rhino早在jdk6的时候已经存在,但现在为何要替代Rhino,官方的解释是Rhino相比其他javascript 引擎(Google V8)实在太慢,要改造Rhino还不如重写。所以Nashorn的性能也是其一个亮点。

    并行流与串行流

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。

    Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

    1.Lambda表达式

    ​ Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

    1.1语法

    Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符箭头操作符。它将 Lambda 分为两个部分:

    左侧:指定了 Lambda 表达式需要的参数列表 (其实就是接口中的抽象方法的形参列表)

    **右侧:**指定了 Lambda ,是抽象方法的实现逻辑,(其实就是重写的抽象方法的方法体)

    1.2使用

    Lambda表达式的使用:(分为6种情况介绍)

    //语法格式一:无参,无返回值
    @Test
    public void test1(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱北京天安门");
            }
        };
        r1.run();
        System.out.println("*******************");
        Runnable r2 = ()-> {
            System.out.println("我爱北京天安门");
        };
        r2.run();
    }
    
    //语法格式二:Lambda 需要一个参数,但是没有返回值。
    @Test
    public void test2(){
        Consumer<String> con1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s + 1);
            }
        };
        con1.accept("谎言和誓言的区别是什么?");
        System.out.println("*******************");
    
        Consumer<String> con2 = (String s)-> System.out.println(s + 2);
        con2.accept("谎言和誓言的区别是什么?");
    }
    
    //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
    @Test
    public void test3(){
        Consumer<String> con1 = (String s) -> {
            System.out.println(s + 1);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
        System.out.println("*******************");
    
        Consumer<String> con2 =  (s) -> {
            System.out.println(s + 2);
        };
        con2.accept("一个是听得人当真了,一个是说的人当真了");
    }
    
    
    //语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
    @Test
    public void test4(){
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
        System.out.println("*******************");
    
        Consumer<String> con2 = s -> {
            System.out.println(s);
        };
        con2.accept("一个是听得人当真了,一个是说的人当真了");
    }
    
    
    //语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
    @Test
    public void test5(){
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                    System.out.println(o1 + o2);
                System.out.println(o1);
            }
        };
        System.out.println(com1.compare(12,21));
        System.out.println("*****************************");
    
        Comparator<Integer> com2 = (o1,o2) -> {
               System.out.println(o1 + o2);
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(111,222));
    }
    
    //语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
    @Test
    public void test6(){
        Comparator<Integer> com1 = (o1,o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(12,6));
        System.out.println("*****************************");
    
        Comparator<Integer> com2 =  (o1,o2) -> o1.compareTo(o2);
        System.out.println(com1.compare(111,222));
    }
    

    1.3总结

    • ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略

    • ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字

    • Lambda表达式的本质:作为函数式接口的实例

    • 如果一个接口中,只声明了一个抽象方法则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。

    • 以前用匿名实现类表示的现在都可以用Lambda表达式来写。

    接口里只有一个抽象方法可以这么写,如果有多个就不行了

    1. 函数式接口,不用知道方法名。2.通过泛型得知参数类型,不用知道具体的数据类型 3. 直接返回调用的结果

    可以有多个方法 但只能有一个抽象方法才是函数式接口@FunctionalInterface

    1.4类型推断

    类型推断

    ​ 在Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

    2.函数式接口

    什么是函数式(Functional)接口

    • 只包含一个抽象方法的接口,称为函数式接口

    • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。

    • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

    • 在java.util.function包下定义了Java 8 的丰富的函数式接口

    2.1理解函数式接口

    • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即**java不但可以支持OOP还可以支持OOF(面向函数编程) **

    • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

    • 简单的说,在Java8中,**Lambda表达式就是一个函数式接口的实例。**这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

    • 以前用匿名实现类表示的现在都可以用Lambda表达式来写。

    2.2接口Api

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

    其他接口

    3. 方法引用

    • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

    • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

    • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!((针对于情况1和情况2))

    • 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。

    • 如下三种主要使用情况:

      对象::实例方法名

      类::静态方法名

      类::实例方法名

    3.1使用

    情况一:对象 :: 实例方法

    //Supplier中的T get()
    //Employee中的String getName()
    @Test
    public void test2() {
        Employee emp = new Employee(1001,"Tom",23,5600);
        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());
        System.out.println("*******************");
    
        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());
    }
    

    情况二:类 :: 静态方法

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    @Test
    public void test4() {
        Function<Double,Long> func1 = d -> Math.round(d);
        System.out.println(func1.apply(12.3));
        System.out.println("*******************");
    
        Function<Double,Long> func2 = Math::round;
        System.out.println(func2.apply(12.6));
    }
    

    情况三:类 :: 实例方法 (有难度)

    // Comparator中的int comapre(T t1,T t2)
    // String中的int t1.compareTo(t2)
    @Test
    public void test5() {
        Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc","abd"));
        System.out.println("*******************");
    
        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abc","abe"));
    
    }
    
    // Function中的R apply(T t)
    // Employee中的String getName();
    @Test
    public void test7() {
        Employee employee1 = new Employee(1001, "Jerry", 23, 6000);
        Function<Employee,String> func1 = e -> e.getName();
        System.out.println(func1.apply(employee1));
        System.out.println("*******************");
        Employee employee2 = new Employee(1001, "tom", 23, 6000);
        Function<Employee,String> func2 = Employee ::getName;
        System.out.println(func2.apply(employee2));
    
    }
    

    注意:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName

    4.构造器与数组|引用

    4.1构造器引用

    格式: ClassName::new

    ​ 与函数式接口相结合,自动与函数式接口中方法兼容。

    ​ 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象

    //BiFunction中的R apply(T t,U u)
    @Test
    public void test3(){
        BiFunction<Integer,String,Employee> func1 = (id, name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"Tom"));
        System.out.println("*******************");
    
        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"Tom"));
    }
    

    4.2数组引用

    格式: type[] :: new

    • 可以把数组看做是一个特殊的类,则写法与构造器引用一致。
      

    //数组引用
    //Function中的R apply(T t)
    @Test
    public void test4(){
        Function<Integer,String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));
        System.out.println("*******************");
    
        Function<Integer,String[]> func2 = String[] :: new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
    

    5.强大的Stream API

    Stream API说明

    • Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API

    • Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 **使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。**简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

    5.1为什么要使用Stream API

    • 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要 Java层面去处理。

    • Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

    什么是 Stream

    Stream到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

    “集合讲的是数据,Stream讲的是计算!”

    注意:

    ①Stream 自己不会存储元素。

    ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

    Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

    5.2Stream 的操作三个步骤

    • 1- 创建 Stream

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

    • 2- 中间操作

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

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

    一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

    5.3 Stream创建方式

    创建 Stream方式一:通过集合

    Java8 中的 Collection 接口被扩展,提供了两个获取流

    的方法:

    • default Stream stream() : 返回一个顺序流

    • default Stream parallelStream() : 返回一个并行流

    //创建 Stream方式一:通过集合
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
        //default Stream<E> stream() : 返回一个顺序流
        Stream<Employee> stream = employees.stream();
    
        // default Stream<E> parallelStream() : 返回一个并行流
        Stream<Employee> parallelStream = employees.parallelStream();
    }
    

    创建 Stream方式二:通过数组

    Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

    static Stream stream(T[] array): 返回一个流

    重载形式,能够处理对应基本类型的数组:

    • public static IntStream stream(int[] array)

    • public static LongStream stream(long[] array)

    • public static DoubleStream stream(double[] array)

    @Test
    public void test2() {
        int[] arr = new int[]{1, 2, 3, 4, 5, 6};
        //调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
        IntStream stream = Arrays.stream(arr);
    
        Employee e1 = new Employee(1001, "Tom");
        Employee e2 = new Employee(1002, "Jerry");
        Employee[] arr1 = new Employee[]{e1, e2};
        Stream<Employee> stream1 = Arrays.stream(arr1);
    }
    

    创建 Stream方式三:通过Stream的of()

    可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

    • public static Stream of(T… values) : 返回一个流
    //创建 Stream方式三:通过Stream的of()
    @Test
    public void test3() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
    }
    

    创建 Stream方式四:创建无限流

    可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。

    迭代

    public static Stream iterate(final T seed, final UnaryOperator f)

    生成

    public static Stream generate(Supplier s)

    //创建 Stream方式四:创建无限流
    @Test
    public void test4() {
        //      迭代
        //      public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        //遍历前10个偶数
        Stream.iterate(0,t -> t + 2).limit(10).forEach(System.out::println);
    
        //      生成
        //      public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }
    

    5.4Stream中间操作

    Stream 的中间操作

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

    1-筛选与切片

    //1-筛选与切片
    @Test
    public void test1(){
        List<Employee> list = EmployeeData.getEmployees();
        //filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
        //练习:查询员工表中薪资大于7000的员工信息
        list.stream().filter( (employee -> employee.getSalary() >= 7000 ) ).forEach(System.out::println);
        System.out.println();
    
    
        //limit(n)——截断流,使其元素不超过给定数量。
        list.stream().limit(3).forEach(System.out::println);
        System.out.println();
    
        //skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
        list.stream().skip(3).forEach(System.out::println);
        System.out.println();
    
        //distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",41,8000));
        list.add(new Employee(1010,"刘强东",41,8000));
        list.stream().distinct().forEach(System.out::println);
    }
    

    2-映射

    //映射
    @Test
    public void test2(){
        //map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        list.stream().map(String::toUpperCase).forEach(System.out::println);
        System.out.println();
    
        //练习1:获取员工姓名长度大于3的员工的姓名。
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
        System.out.println();
    
        //练习2:
        Stream<Stream<Character>> streamStream = list.stream().map(SteamApi_02_use::fromStringToStream);
        streamStream.forEach( (s) -> System.out.println(s.collect(Collectors.toList())));
        System.out.println();
    
    
        //flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
        Stream<Character> characterStream = list.stream().flatMap(SteamApi_02_use::fromStringToStream);
        characterStream.forEach(System.out::println);
    }
    
    //将字符串中的多个字符构成的集合转换为对应的Stream的实例
    public static Stream<Character> fromStringToStream(String str){//aa
        ArrayList<Character> list = new ArrayList<>();
        for(Character c : str.toCharArray()){
            list.add(c);
        }
        return list.stream();
    }
    

    3-排序

    //排序
    @Test
    public void test4(){
        //sorted()——自然排序
        List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
        list.stream().sorted().forEach(System.out::println);
        System.out.println();
    
    
        //sorted(Comparator com)——定制排序
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted( (e1,e2) -> e1.getAge() - e2.getAge() ).forEach(System.out::println);
    }
    

    5.5Stream终止操作

    • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

    • 流进行了终止操作后,不能再次使用。

    1-匹配与查找

    //1-匹配与查找
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
    
        //allMatch(Predicate p)——检查是否匹配所有元素。
        //练习:是否所有的员工的年龄都大于18
        boolean flag = employees.stream().allMatch(employee -> employee.getAge() > 18);
        System.out.println(flag);
    
        //anyMatch(Predicate p)——检查是否至少匹配一个元素。
        //练习:是否存在员工的工资大于 10000
        flag = employees.stream().anyMatch(employee -> employee.getSalary() > 10000);
        System.out.println(flag);
    
        //noneMatch(Predicate p)——检查是否没有匹配的元素。
        //练习:是否存在员工姓“雷”
        flag = employees.stream().noneMatch(employee -> employee.getName().startsWith("雷"));
        System.out.println(flag);
    
        //findFirst——返回第一个元素
        Optional<Employee> employee = employees.stream().findFirst();
        System.out.println(employee);
    
        //findAny——返回当前流中的任意元素
        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println(any);
    }
    
    @Test
    public void test2() {
        List<Employee> employees = EmployeeData.getEmployees();
        // count——返回流中元素的总个数
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println(count);
        System.out.println();
    
        //max(Comparator c)——返回流中最大值
        //练习:返回最高的工资:
        Optional<Double> max = employees.stream().map(e -> e.getSalary()).max((m1, m2) -> (int) (m1 - m2));
        System.out.println(max);
        System.out.println();
    
        //min(Comparator c)——返回流中最小值
        //练习:返回最低工资的员工
        Optional<Employee> min = employees.stream().min((e1, e2) -> (int) (e1.getSalary() -  e2.getSalary()));
        System.out.println(min);
        System.out.println();
    
        //forEach(Consumer c)——内部迭代
        employees.stream().forEach(System.out::println);
        System.out.println();
    
    
        //使用集合的遍历操作 --外部迭代
        employees.forEach(System.out::println);
    }
    

    2-归约

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

    //2-归约
    @Test
    public void test3() {
        //reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
        //练习1:计算1-10的自然数的和
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    
        Integer sum1 = list.stream().reduce(0, Integer::sum);
        Integer sum2 = list.stream().reduce(0, (s1,s2)-> s1 + s2);
        System.out.println(sum1);
        System.out.println(sum2);
        System.out.println();
    
        //reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
        //练习2:计算公司所有员工工资的总和
        List<Employee> employees = EmployeeData.getEmployees();
        Optional<Double> sumMoney1 = employees.stream().map(Employee::getSalary).reduce(Double::sum);
        Optional<Double> sumMoney2 = employees.stream().map(Employee::getSalary).reduce((s1,s2)->s1 + s2);
        System.out.println(sumMoney1);
        System.out.println(sumMoney2);
        //Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
    
    
    }
    

    3-收集

    Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

    另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coJaIPz8-1647832774209)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20220320140651092.png)]

    //3-收集
    @Test
    public void test4() {
        //collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
        //练习1:查找工资大于6000的员工,结果返回为一个List或Set
        List<Employee> employees = EmployeeData.getEmployees();
        List<Employee> collect = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
        collect.forEach(System.out::println);
    
    }
    

    6.Optional类

    • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

    • Optional 类(java.util.Optional) 是一个容器类它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

    • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

    6.1使用

    • Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    • 创建Optional类对象的方法:

      • Optional.of(T t) : 创建一个 Optional 实例,t必须非空;

      • Optional.empty() : 创建一个空的 Optional 实例

      • Optional.ofNullable(T t):t可以为null

    • 判断Optional容器中是否包含对象:

    • boolean isPresent() : 判断是否包含对象

    • void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

    • 获取Optional容器的对象:

      • T get(): 如果调用对象包含值,返回该值,否则抛异常

      • T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。

      • T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

      • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

    @Test
    public void test3(){
        Boy boy = null;
        //boy = new Boy();
        //boy = new Boy(new Girl("苍老师"));
        String girlName = getGirlName(boy);
        System.out.println(girlName);
    }
    
    //使用Optional类的getGirlName():
    public String getGirlName(Boy boy){
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        //此时的boy1一定非空
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
        Girl girl = boy1.getGirl();
    
        Optional<Girl> girlOptional = Optional.ofNullable(girl);
        //girl1一定非空
        Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
    
        return girl1.getName();
    }
    

    7.新时间日期和API

    7.1使用LocalDate、LocalTime、LocalDateTime

    LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

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

    LocalDateTime dateTime1 = LocalDateTime.now();
    System.out.println(dateTime1);
    
    LocalDateTime dateTime2 = LocalDateTime.of(2016, 11, 21, 10, 10);
    System.out.println(dateTime2);
    //增加
    LocalDateTime dateTime3 = dateTime2.plusDays(5);
    System.out.println(dateTime3);
    //减少
    LocalDateTime dateTime4 = dateTime3.minusDays(2);
    System.out.println(dateTime4);
    

    7.2Instant 时间戳

    ⚫ 用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算

    Instant ins = Instant.now();  //默认使用 UTC 时区
    System.out.println(ins);
    System.out.println(ins.getEpochSecond());
    System.out.println(ins.toEpochMilli());
    
    Instant ins2 = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));  //东8区
    System.out.println(ins2);
    System.out.println(ins2.getEpochSecond());
    System.out.println(ins2.toEpochMilli());
    

    7.3Duration 和 Period

    ⚫ Duration:用于计算两个“时间”间隔

    ⚫ Period:用于计算两个“日期”间隔

    Instant ins1 = Instant.now();
    Thread.sleep(1000);
    Instant ins2 = Instant.now();
    System.out.println("所耗费时间为:" + Duration.between(ins1, ins2));
    
    System.out.println("----------------------------------");
    
    LocalDate ld1 = LocalDate.now();
    LocalDate ld2 = LocalDate.of(2011, 1, 1);
    System.out.println(Period.between(ld1,ld2));
    

    7.4日期的操纵

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

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

    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1);
    
    LocalDateTime ldt2 = ldt1.withDayOfMonth(10);
    System.out.println(ldt2);
    
    //TemporalAdjusters 静态方法对TemporalAdjuster的实现 | 下个周日
    LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println(ldt3);
    
    //TemporalAdjuster 自定义:下个工作日
    LocalDateTime ldt4 = ldt1.with(l->{
        LocalDateTime ldt5 = (LocalDateTime) l;
    
        DayOfWeek dayOfWeek = ldt5.getDayOfWeek();
        if(dayOfWeek.equals(DayOfWeek.FRIDAY)){
            return ldt5.plusDays(3);
        }else if(dayOfWeek.equals(DayOfWeek.SATURDAY)){
            return ldt5.plusDays(2);
        }else{
            return ldt5.plusDays(1);
        }
    });
    System.out.println(ldt4);
    
    

    7.5解析与格式化

    java.time.format.DateTimeFormatter类:该类提供了三种格式化方法:

    ⚫ 预定义的标准格式

    ⚫ 语言环境相关的格式

    ⚫ 自定义的格式

    
    LocalDateTime ldt = LocalDateTime.now();
    
    //API的日期格式
    DateTimeFormatter dtf1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    String strDate1 = ldt.format(dtf1);
    System.out.println(strDate1);
    
    //自定义日期格式
    DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
    String strDate2 = ldt.format(dtf2);
    System.out.println(strDate2);
    
    //字符串时间转换为LocalDateTime
    LocalDateTime dateTime =  LocalDateTime.parse(strDate2, dtf2);
    System.out.println(dateTime);
    

    7.6时区的处理

    ⚫ Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime

    其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式

    例如 :Asia/Shanghai 等

    ZoneId:该类中包含了所有的时区信息

    getAvailableZoneIds() : 可以获取所有时区时区信息

    of(id) : 用指定的时区信息获取ZoneId 对象

    //获取所有的时区
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println);
    
    LocalDateTime ldt = LocalDateTime.now(ZoneId.of("US/Pacific"));
    System.out.println(ldt);
    
    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
    System.out.println(zdt);
    

    7.7与传统日期处理的转换

    8.接口中的默认方法与静态方法

    Java8之前的接口中只能定义全局常量,抽象方法,

    Java8之后的接口中能定义全局常量,抽象方法,默认方法以及静态方法

    8.1默认方法

    Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。

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

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

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

    情况一:

    情况二:

    8.2接口中的静态方法

    Java8 中,接口中允许添加静态方法。

    9.重复注解与类型注解

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

    PS需要的实体类

    public class Employee {
    
    	private int id;
    	private String name;
    	private int age;
    	private double salary;
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	public double getSalary() {
    		return salary;
    	}
    
    	public void setSalary(double salary) {
    		this.salary = salary;
    	}
    
    	public Employee() {
    		System.out.println("Employee().....");
    	}
    
    	public Employee(int id) {
    		this.id = id;
    		System.out.println("Employee(int id).....");
    	}
    
    	public Employee(int id, String name) {
    		this.id = id;
    		this.name = name;
    	}
    
    	public Employee(int id, String name, int age, double salary) {
    
    		this.id = id;
    		this.name = name;
    		this.age = age;
    		this.salary = salary;
    	}
    
    	@Override
    	public String toString() {
    		return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
    	}
    
    	@Override
    	public boolean equals(Object o) {
    		if (this == o)
    			return true;
    		if (o == null || getClass() != o.getClass())
    			return false;
    
    		Employee employee = (Employee) o;
    
    		if (id != employee.id)
    			return false;
    		if (age != employee.age)
    			return false;
    		if (Double.compare(employee.salary, salary) != 0)
    			return false;
    		return name != null ? name.equals(employee.name) : employee.name == null;
    	}
    
    	@Override
    	public int hashCode() {
    		int result;
    		long temp;
    		result = id;
    		result = 31 * result + (name != null ? name.hashCode() : 0);
    		result = 31 * result + age;
    		temp = Double.doubleToLongBits(salary);
    		result = 31 * result + (int) (temp ^ (temp >>> 32));
    		return result;
    	}
    }
    
    public class EmployeeData {
    	public static List<Employee> getEmployees(){
    		List<Employee> list = new ArrayList<>();
    		
    		list.add(new Employee(1001, "马化腾", 34, 6000.38));
    		list.add(new Employee(1002, "马云", 12, 9876.12));
    		list.add(new Employee(1003, "刘强东", 33, 3000.82));
    		list.add(new Employee(1004, "雷军", 26, 7657.37));
    		list.add(new Employee(1005, "李彦宏", 65, 5555.32));
    		list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
    		list.add(new Employee(1007, "任正非", 26, 4333.32));
    		list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
    		
    		return list;
    	}
    }
    

    salary + ‘}’;
    }

    @Override
    public boolean equals(Object o) {
    	if (this == o)
    		return true;
    	if (o == null || getClass() != o.getClass())
    		return false;
    
    	Employee employee = (Employee) o;
    
    	if (id != employee.id)
    		return false;
    	if (age != employee.age)
    		return false;
    	if (Double.compare(employee.salary, salary) != 0)
    		return false;
    	return name != null ? name.equals(employee.name) : employee.name == null;
    }
    
    @Override
    public int hashCode() {
    	int result;
    	long temp;
    	result = id;
    	result = 31 * result + (name != null ? name.hashCode() : 0);
    	result = 31 * result + age;
    	temp = Double.doubleToLongBits(salary);
    	result = 31 * result + (int) (temp ^ (temp >>> 32));
    	return result;
    }
    

    }

    
    
    
    ```java
    public class EmployeeData {
    	public static List<Employee> getEmployees(){
    		List<Employee> list = new ArrayList<>();
    		
    		list.add(new Employee(1001, "马化腾", 34, 6000.38));
    		list.add(new Employee(1002, "马云", 12, 9876.12));
    		list.add(new Employee(1003, "刘强东", 33, 3000.82));
    		list.add(new Employee(1004, "雷军", 26, 7657.37));
    		list.add(new Employee(1005, "李彦宏", 65, 5555.32));
    		list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
    		list.add(new Employee(1007, "任正非", 26, 4333.32));
    		list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
    		
    		return list;
    	}
    }
    
    展开全文
  • 尚硅谷Java8新特性下载

    热门讨论 2017-01-22 11:30:18
    尚硅谷 Java8 新特性 百度云下载链接
  • java 8新特性.md

    2020-02-09 12:49:35
    java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述,java 8新特性描述java 8新特性描述,java...
  • Java8新特性——新一套日期时间API

    万次阅读 多人点赞 2021-11-09 15:44:15
    文章目录: ...8.DateTimeFormatter 1.新旧对比(线程安全问题) 我们先来看下面的代码:???????????? (关于代码中某些类中的某些方法,我在这里就不说了,大家可以去查找api文档) package co

    文章目录:

    1.新旧对比(线程安全问题)

    2.LocalDate

    3.LocalTime

    4.LocalDateTime

    5.Instant

    6.Duration、Period

    7.TestTemporalAdjuster、TestTemporalAdjusters

    8.DateTimeFormatter


    1.新旧对比(线程安全问题)

    我们先来看下面的代码:👇👇👇 (关于代码中某些类中的某些方法,我在这里就不说了,大家可以去查找api文档)

    package com.szh.java8.datetime;
    
    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.*;
    
    /**
     *
     */
    public class TestSimpleDateFormat {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    
            Callable<Date> task1 = new Callable<Date>() {
                @Override
                public Date call() throws Exception {
                    return sdf.parse("20211109");
                }
            };
    
            ExecutorService pool1 = Executors.newFixedThreadPool(10);
            List<Future<Date>> futureList1 = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                futureList1.add(pool1.submit(task1));
            }
    
            for (Future<Date> future : futureList1) {
                System.out.println(future.get());
            }
    
            pool1.shutdown();
    
            //=================================================================
    
    //        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
    //
    //        Callable<LocalDate> task2 = new Callable<LocalDate>() {
    //            @Override
    //            public LocalDate call() throws Exception {
    //                return LocalDate.parse("20211109",dtf);
    //            }
    //        };
    //
    //        ExecutorService pool2 = Executors.newFixedThreadPool(10);
    //        List<Future<LocalDate>> futureList2 = new ArrayList<>();
    //        for (int i = 0; i < 10; i++) {
    //            futureList2.add(pool2.submit(task2));
    //        }
    //
    //        for (Future<LocalDate> future : futureList2) {
    //            System.out.println(future.get());
    //        }
    //
    //        pool2.shutdown();
        }
    }
    

    运行之后,就出现了线程安全问题。

    将代码中的上半部分注释掉,然后打开下半部分的代码,再次运行,线程安全问题就不存在了。

    也就是Java8中提供了新一套日期时间API已经解决了线程安全问题。


    2.LocalDate

    package com.szh.java8.datetime;
    
    import java.time.LocalDate;
    
    /**
     *
     */
    public class TestLocalDate {
    
        public static void main(String[] args) {
            LocalDate ld1 = LocalDate.now();
            System.out.println(ld1);
    
            LocalDate ld2 = LocalDate.of(2021,5,1);
            System.out.println(ld2);
    
            LocalDate ld3 = ld1.plusYears(20);
            System.out.println(ld3);
    
            LocalDate ld4 = ld1.minusMonths(3);
            System.out.println(ld4);
    
            System.out.println(ld1.isBefore(ld2));
            System.out.println(ld2.isAfter(ld1));
            System.out.println(ld1.isLeapYear());
    
            System.out.println("年:" + ld1.getYear() + ", 月:" + ld1.getMonth() + ", 日:" + ld1.getDayOfMonth());
            System.out.println("年:" + ld1.getYear() + ", 月:" + ld1.getMonthValue() + ", 日:" + ld1.getDayOfMonth());
        }
    }
    

    3.LocalTime

    package com.szh.java8.datetime;
    
    import java.time.LocalTime;
    
    /**
     *
     */
    public class TestLocalTime {
    
        public static void main(String[] args) {
            LocalTime lt1 = LocalTime.now();
            System.out.println(lt1);
    
            LocalTime lt2 = LocalTime.of(13,14,15);
            System.out.println(lt2);
    
            LocalTime lt3 = lt2.plusHours(3);
            System.out.println(lt3);
    
            LocalTime lt4 = lt2.minusMinutes(14);
            System.out.println(lt4);
    
            System.out.println(lt1.isBefore(lt2));
            System.out.println(lt2.isAfter(lt1));
            System.out.println("小时:" + lt1.getHour() + ", 分钟:" + lt1.getMinute() + ", 秒:" + lt1.getSecond());
        }
    }
    

    4.LocalDateTime

    package com.szh.java8.datetime;
    
    import java.time.LocalDateTime;
    
    /**
     *
     */
    public class TestLocalDateTime {
    
        public static void main(String[] args) {
            LocalDateTime ldt1 = LocalDateTime.now();
            System.out.println(ldt1);
    
            LocalDateTime ldt2 = LocalDateTime.of(2020,5,1,13,14,15);
            System.out.println(ldt2);
    
            LocalDateTime ldt3 = ldt1.plusYears(15);
            System.out.println(ldt3);
    
            LocalDateTime ldt4 = ldt1.minusDays(20);
            System.out.println(ldt4);
    
            System.out.println(ldt1.isBefore(ldt2));
            System.out.println(ldt2.isAfter(ldt1));
            System.out.println("年:" + ldt2.getYear() + ", 月:" + ldt2.getMonthValue() + ", 日:" + ldt2.getDayOfMonth()
                    + ", 小时:" + ldt2.getHour() + ", 分钟:" + ldt2.getMinute() + ", 秒:" + ldt2.getSecond());
        }
    }
    

    5.Instant

    package com.szh.java8.datetime;
    
    import java.time.Instant;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    
    /**
     * Instant : 时间戳(使用 Unix 元年  1970年1月1日 00:00:00 所经历的毫秒值)
     *           默认使用 UTC 时区
     */
    public class TestInstant {
    
        public static void main(String[] args) {
            Instant instant1 = Instant.now();
            System.out.println(instant1);
    
            OffsetDateTime odt = instant1.atOffset(ZoneOffset.ofHoursMinutesSeconds(8,16,32));
            System.out.println(odt);
    
            System.out.println(instant1.getEpochSecond());
            System.out.println(instant1.toEpochMilli());
    
            Instant instant2 = Instant.ofEpochSecond(1000);
            System.out.println(instant2);
    
            Instant instant3 = instant1.plusSeconds(30);
            System.out.println(instant3);
    
            Instant instant4 = instant1.minusSeconds(50);
            System.out.println(instant4);
        }
    }
    

    6.Duration、Period

    package com.szh.java8.datetime;
    
    import java.time.Duration;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import java.time.Period;
    
    /**
     * Period : 用于计算两个“日期”间隔
     * Duration : 用于计算两个“时间”间隔
     */
    public class TestPeriodDuration {
    
        public static void main(String[] args) {
            LocalDate ld1 = LocalDate.now();
            LocalDate ld2 = LocalDate.of(2020,5,1);
            Period period = Period.between(ld2,ld1);
            System.out.println("两个日期相差:" + period.getYears() + "年," + period.getMonths() + "个月,"
                                + period.getDays() + "天....");
            System.out.println(period.isNegative()); //检查此期间的三个单位是否为负
            System.out.println(period.isZero()); //检查此期间的所有三个单位是否为零
    
            System.out.println("--------------------------------------------");
    
            LocalTime lt1 = LocalTime.now();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LocalTime lt2 = LocalTime.now();
            Duration duration = Duration.between(lt1,lt2);
            System.out.println("两个时间相差:" + duration.toHours() + "个小时," + duration.toMinutes() + "分钟,"
                                + duration.getSeconds() + "秒....");
            System.out.println(duration.isNegative()); //检查此期间的三个单位是否为负
            System.out.println(duration.isZero()); //检查此期间的所有三个单位是否为零
        }
    }
    

    7.TestTemporalAdjuster、TestTemporalAdjusters

    package com.szh.java8.datetime;
    
    import java.time.DayOfWeek;
    import java.time.LocalDateTime;
    import java.time.temporal.TemporalAdjusters;
    
    /**
     *  TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
     *  TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。
     */
    public class TestTemporalAdjuster {
    
        public static void main(String[] args) {
            LocalDateTime ldt1 = LocalDateTime.now();
            System.out.println(ldt1);
    
            LocalDateTime ldt2 = ldt1.withMonth(5);
            System.out.println(ldt2);
    
            LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
            System.out.println(ldt3);
    
            //自定义:下一个工作日
            LocalDateTime ldt4 = ldt1.with((l) -> {
                LocalDateTime ldt5 = (LocalDateTime) l;
                DayOfWeek dow = ldt5.getDayOfWeek();
    
                if (dow.equals(DayOfWeek.FRIDAY)) {
                    return ldt5.plusDays(3);
                } else if (dow.equals(DayOfWeek.SATURDAY)) {
                    return ldt5.plusDays(2);
                } else {
                    return ldt5.plusDays(1);
                }
            });
            System.out.println(ldt4);
        }
    }
    

    8.DateTimeFormatter

    package com.szh.java8.datetime;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    /**
     * 解析与格式化
     */
    public class TestDateTimeFormatter {
    
        public static void main(String[] args) {
            DateTimeFormatter dtf1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
            LocalDateTime ldt1 = LocalDateTime.now();
    
            String strDate1 = ldt1.format(dtf1);
            System.out.println(strDate1);
    
            System.out.println("-----------------------------------");
    
            DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
            String strDate2 = ldt1.format(dtf2);
            System.out.println(strDate2);
    
            System.out.println("-----------------------------------");
    
            LocalDateTime newDate = ldt1.parse(strDate2, dtf2);
            System.out.println(newDate);
        }
    }
    

    展开全文
  • Java8新特性DateTime使用

    千次阅读 2022-02-12 13:08:19
    Java8新特性DateTime使用
  • java 面试高频问题 java8新特性

    千次阅读 2019-02-13 13:24:07
    之前面试几家公司,很频繁的问到了java8新特性的问题。这里总结一下: 部分转载于java8新特性 目录 1.介绍 2.Java语言新特性 2.1 Lambda表达式 2.2 接口的默认方法与静态方法  2.3 方法引用 2.4 重复注解 ...
  • Java 8 新特性 及 常见 面试题

    万次阅读 多人点赞 2018-09-22 21:34:44
    Java 8 新特性简介: 1. 代码更少(增加了语法:Lambda 表达式) 2. 强大的 Stream API(集合数据的操作) 3. 最大化的减少空指针 异常:Optional 类 的使用 4. 接口的新特性 5. 注解的新特性 6. 集合的底层 源码...
  • Java8新特性之Lambda表达式

    万次阅读 多人点赞 2021-08-25 14:47:23
    Lambda目录 前言 一、Lambda表达式有哪些语法? 1.1.语法一(无参数,无返回值) 1.2....在java8中,提供了很多函数式接口,可以用于描述各种Lambda表达式的签名 3.3.隐式返回 3.4.访问本地变量(值闭包) 3.5.接收lambdas
  • Java8新特性之双冒号(::)

    千次阅读 2022-03-13 20:08:18
    双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。 大概意思就是,使用lambda表达式会创建匿名函数, 但有时候需要使用一个lambda表达式只调用一个...
  • Java中的排序问题(Java8新特性 stream流、stream多字段排序): 学习内容: 一、什么是流 二、stream特性 一、概要 Java 8 API添加了一个的抽象成为流Stream,它以一种声明的方式处理数据,最后由最终操做获得...
  • Java8 新特性 3】java8 supplier

    千次阅读 多人点赞 2020-08-14 18:04:03
    一、Java8新特性,Supplier 1、Supplier简介 supplier接口是JAVA8以后配合lambda表达式和函数式接口编程(FunctionInterface,以下简称FI)组合使用的一个接口,对外表现为双冒号"::",顺便说下"->"符号对应的是...
  • Java8新特性之方法引用

    千次阅读 2020-06-11 00:51:12
    方法引用就是通过方法的名字来指向一个方法。它可以使语言的构造更紧凑简洁,减少冗余代码。方法引用的操作符是双冒号`::`。
  • java8新特性 使用stream排序(多字段) List<类> list; 代表某集合 //返回 对象集合以类属性一升序排序 list.stream().sorted(Comparator.comparing(类::属性一)); //返回 对象集合以类属性一降序排序 ...
  • Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性Java8新特性
  • 使用JAVA8新特性实现笛卡尔积

    千次阅读 2020-06-12 17:30:07
    以前实现笛卡尔积比较繁琐,使用java8新特性提高了代码的简洁度,也更容易实现 import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * @...
  • Java 8 新特性Java Supplier示例

    千次阅读 2021-05-07 17:05:57
    Supplier在Java 8中被引入,属于java.util.function包。 Supplier功能接口的源代码如下。 @FunctionalInterface public interface Supplier<T> { T get(); } 我们可以看到在上面的代码中,Supplier有ge
  • Java8新特性

    万次阅读 2018-10-07 20:23:15
    Lambda表达式、Stream Api、Optional类
  • java8新特性-stream对map集合进行过滤的方法 stream对map集合进行过滤的方法
  • java8 新特性 Stream流对list进行去重

    千次阅读 2020-09-15 09:33:42
    java8 新特性 Stream流对list进行多条件去重前言数据准备通过单个数据进行去重操作通过多条件去重操作 前言 在日常开发工作中,可能会遇到一些数据去重的问题,首先可以从数据源中进行去重,比如MySQL的Group By 和 ...
  • java8新特性 获取list某一列的操作

    千次阅读 2021-03-09 08:08:18
    提取某一列(以name为例)//从对象列表中提取一列(以name为例)List nameList = studentList.stream().map(StudentInfo::getName).collect(Collectors.toList());//提取后输出namenameList....补充:java8新特性:lam...
  • 遍历的时候想使用java8新特性,却提示图下图 想了一会,发现因为我的是maven的父子工程,所以模块还需要配置java的版本 模块的语言级别还需要配置为java8版本的 不报错了,解决问题了 ...
  • JAVA8十大新特性详解

    万次阅读 多人点赞 2019-08-14 10:34:24
    前言: Java ...在Java Code Geeks上已经有很多介绍Java 8新特性的文章, 例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract C...
  • 在本页中,我们将提供java 8 Stream filter()示例。 它是一个中间操作,可以与reduce()、collect()和map()等一起使用。 我们为给定的Predicate实例过滤集合。 filter()方法返回一个流实例,该实例只包含基于给定...
  • Java 8中允许接口中包含具有具体实现的方法,该方法称为 “默认方法” ,默认方法使用 default 关键字修饰。 接口默认方法的 ” 类优先 ” 原则。若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了...
  • 标题Map通过Java8新特性流过滤NULL值或对象 运行例子: 代码块: Map<Integer, Integer> collect = map.entrySet().stream().filter((e) -> e.getValue() != null).collect(Collectors.toMap( (e) -...
  • Java8新特性(五)Nashorn javascript引擎

    万次阅读 2018-03-18 13:48:07
    Java8之前,如果要调用js语言是用Rhino调用,Java8只会自动升级到Nashorn,下面有一个例子 //获取脚本引擎的对象 ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine engine = sem....
  • Java 8 新特性Java Stream通过peek在调用子元素时执行方法1. Stream.peek() : 是一个中间操作(`An Intermediate Operation`)2.peek()示例`peek()`并行流(`Parallel Stream`)示例Java 9中的`peek()`和`count()`...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 563,590
精华内容 225,436
关键字:

java8新特性