zoukankan      html  css  js  c++  java
  • Java8新特性(一)——Lambda表达式与函数式接口

    一、Java8新特性概述

      1.Lambda 表达式

      2. 函数式接口

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

      4. Stream API

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

      6. 新时间日期 API

      7. 其他新特性

      // 其他例如HashMap在JDK8中的提升,将会在HashMap的章节进行拓展

     二、Lambda表达式

      1.为什么使用Lambda

      Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。

      可以写出更简洁、更 灵活的代码。

      2.从匿名内部类到λ表达式

      回顾Java基础中匿名内部类的写法:

      // 原来的匿名内部类
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable!");
            }
        };

      实际上,上述代码中核心的只有输出语句那一句,首先这个匿名内部类没有名字,其次我们实现Runnable接口时其实就是去重写run()方法这一个方法

    所以,如果Java可以自动地帮我们感应出除核心代码之外的东西,那么代码便可以大大简化简洁!

      改造为Lambda表达式:

         // JDK8的Lambda表达式
            Runnable r8 = () -> System.out.println("Runnable!");

      // 这里赞一下IDEA,像λ这样的新特性,他也是可以自动感应进行标记的

       关于Lambda表达式更多更实用的案例,可以参见如下代码:

    package com.atguigu.java8;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    import java.util.TreeSet;
    
    import org.junit.Test;
    
    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);
        }
    }
    View Code

       3.Lambda表达式语法格式

        比较关键的是箭头操作符(->)将表达式拆分成两部分:

          左侧——参数列表

          右侧——拉姆达体(实现功能的代码)

        对应到上文中示例的写法,Lambda表达式对应的是实现了接口的匿名内部类,那么左侧的参数列表就对应了它所实现的接口中抽象方法的

    参数列表,而右边就是抽象方法的 实现(关于若接口中有多个抽象方法,将会在函数式接口中进行阐述)

        我们通过以下一些实例结合注释来加强对拉姆达表达式格式的理解:

           // 语法格式一:无参数数无返回值
            Runnable r8 = () -> System.out.println("Runnable!");
            // 语法格式二:有一个参数无返回值(Consumer为JDK的一个接口,具体可以参考源码)
            Consumer<String> con = (x) -> System.out.println(x);
            con.accept("Hello Lambda!");
            // 语法格式三:只有一个参数时,小括号可以省略(当然不太建议省略)
            Consumer<String> consumer = x -> System.out.println(x);
            // 语法格式四:多个参数、多条语句(使用大括号)、有返回值
            Comparator<Integer> c1 = (x, y) -> {
                System.out.println("函数式接口!");
                return Integer.compare(x, y);
            };
            // 语法格式五:多个参数、只有一条语句(可以省略大括号)、有返回值(可以省略return)
            Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
            // 语法格式六:Lambda表达式的参数列表的参数类型可以省略!是因为JVM编译时可以通过上下文推断出数据类型:也就是类型推断!

      当然了,本质上Lambda表达式还是一个语法糖,但是如此简洁的表达一方面使得我们的代码变得简洁,但另外一方面也给调试 和维护的人员带来了不小的麻烦(尤其是还未接触JDK8新特性的人),所以即使好用,也需要慎用! 

    三、函数式接口

      什么是函数式接口

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

      由上文我们也知道Lambda表达式是依赖函数式接口的(不然它也不知道它所实现的接口对应的是哪个方法),(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)

      可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口

    @FunctionalInterface
    public interface Flyable<T> {
        int fly(T t);
    }

      一个基本的示例如下:

    // 接口
    @FunctionalInterface
    public interface Oprator {
        Integer oprator(Integer num1);
    }
    
     public Integer oprator(Integer num, Oprator op){
            return op.oprator(num);
        }
     oprator(10, (x) -> x + x);

       四大内置核心函数式接口

      通过Lambda表达式呢我们可以写出非常简洁的代码,但是也暴露出一个问题:每实现一个功能(例如上文的操作一个数,或者其他的比较两个数以及更多的需求功能),每一个我们都必须新建一个相应的函数式接口——即使它只有一个抽象方法,这就导致了接口的暴涨,于是Java8中引入了内置函数式接口来解决此问题!

      像更新了函数式接口后,集合的遍历也变得更加简单(虽然底层不变),例如list.forEach(),传入一个消费式接口即可(参见源码!)

      

       我们通过一段代码加注释来进行学习了解:

    // 1.Consumer<T>——消费型接口——“有去无回”
        public void happy(double money, Consumer<Double> con) {
            // 利用Consumer接口进行消费
            con.accept(money);
        }
        @Test
        public void test1() {
            // 单参数、无返回值
            happy(100, (x) -> System.out.println("‘大保健消费...’"));
        }
    
        // 2.Supplier<T>——供给型接口——“空手套白狼”
        public List<Integer> getNumList(int num, Supplier<Integer> sup) {
            // 利用Supplier接口产生指定个数个数字放入集合并返回
            List<Integer> list = new ArrayList<>();
            Random random = new Random();
            for (int i = 0; i<num; i++) {
                list.add(sup.get());
            }
            return list;
        }
        @Test
        public void test2() {
            // 无参数,有返回值
            List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
            System.out.println(numList);
        }
    
        // 3.Function<T, R>——函数型接口——“礼尚往来”
        public String strHandler(String str, Function<String, String> fun) {
            // 对字符串进行处理(具体处理逻辑将在Lambda表达式中实现)
            return fun.apply(str);
        }
        @Test
        public void test3() {
            // 单参数、有返回值
            String s = strHandler("hello", (x) -> x.toUpperCase());
            System.out.println(s);
        }
    
        // 4.Predicate<T>——断言型接口——“明辨是非”
        public List<String> filterStr(List<String> list, Predicate<String> pre) {
            List<String> strList = new ArrayList<>();
            for (String s : list) {
                // 对符合条件的字符串进行过滤
                if (pre.test(s)) {
                    strList.add(s);
                }
            }
            return strList;
        }
        @Test
        public void test4() {
            List<String> list = Arrays.asList("hello", "world", "Hello");
            // 单参数,有返回值(boolean)
            List<String> list1 = filterStr(list, (x) -> x.equalsIgnoreCase("hello"));
            System.out.println(list1);
        }

       当然如果四大内置函数接口无法满足的话,可以尝试其他的拓展接口:未全部列出,可以去java.lang.function中查看(注意JDK版本)

      

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

      方法引用

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

     (实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)

      方法引用是Lambda表达式的一种形式!

      方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来

        对象::实例方法

         类::静态方法

         类::实例方法

      先来看一个实例:

      @Test
        public void test1() {
            // Lambda的写法
            Consumer<String> con1 = (x) -> System.out.println(x);
    //        也就是System.out.println()的返回值和参数列表与接口Consumer中抽象方法一致
    //        PrintStream ps = System.out;
    //        Consumer<String> con2 = ps::println;
            // 方法引用的写法  对象::实例方法名
            Consumer<String> con2 = System.out::println;
            con2.accept("hello method reference");
        }
        @Test
        public void test2() {
            Employee emp = new Employee("小明", 18);
            // Lambda写法
            Supplier<String> sup1 = () -> emp.getName();
            // 方法引用(注意这里的方法是不需要写小括号的)
            Supplier<String> sup2 = emp::getName;
            String s = sup2.get();
            System.out.println(s);
        }

      从上文也可以看出,方法引用的条件是Lambda表达式中的方法体已经有方法实现了(该方法的参数列表与返回值一致!)

      上述示例就是通过对象::实例方法的形式写出的方法引用,接下来介绍其他的几种形式:注意点见注释!

     // 类::静态方法的形式
        @Test
        public void test3() {
            // Lambda表达式的写法
            Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y);
            // 改造为方法引用(这里使用的是Integer的静态方法)
            Comparator<Integer> c2 = Integer::compare;
        }
        // 类::实例方法的形式
        @Test
        public void test4() {
            // Lambda方法
            BiPredicate<String, String> bp = (x, y) -> x.equalsIgnoreCase(y);
            // 方法引用,需要满足条件:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引 用方法的第二个参数(或无参数)时
            BiPredicate<String, String> bp2 = String::equalsIgnoreCase;
        }

      构造器引用

      语法形式:ClassName::new

      // 构造器引用
        @Test
        public void test5() {
            // Lambda表达式
            Supplier<Employee> sup1 = () -> new Employee();
            // 方法引用(这里自动匹配无参构造器),实际中是按照抽象方法中有几个参数便调用几个参数的构造器
            Supplier<Employee> sup2 = Employee::new;
        }
  • 相关阅读:
    Codeforces Round #535 (Div. 3)
    2019 CCPC-Wannafly Winter Camp Day4(Div2, onsite)
    Codeforces Round #534 (Div. 2)
    2019 CCPC-Wannafly Winter Camp Day3(Div2, onsite)
    2019 CCPC-Wannafly Winter Camp Day2(Div2, onsite)
    2019 CCPC-Wannafly Winter Camp Day1 (Div2, onsite)
    codeforces1097D Makoto and a Blackboard 数学+期望dp
    【NOIP2016】换教室
    ICPC2019徐州站游记
    【Codeforces】Orz Panda Cup
  • 原文地址:https://www.cnblogs.com/jiangbei/p/7588417.html
Copyright © 2011-2022 走看看