Lambda表达式详解

什么是Lambda表达式

Lambda表达式是Java 8的新特性,是函数式接口的实例。使用Lambda表达式可以写出更简洁、更灵活的代码。

Lambda表达式语法

Lambda语法格式:

(parameters) -> expression
// 或
(parameters) -> { statements; }

->:叫做Lambda操作符箭头操作符

->左边:Lambda形参列表(其实就是借口中的抽象方法的形参列表)

->右边:Lambda体(其实就是重写的抽象方法的方法体)

Lambda表达式的使用

这里我们将Lambda表达式的使用细分为6中情况,下面结合例子进行介绍:

  1. 情况一:无参,无返回值

    public class LambdaTest {
        //语法格式一:无参,无返回值
        @Test
        public void test1() {
         //普通写法
            Runnable r1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println("公众号:程序员汪汪------关注我");
                }
            };
            r1.run();
            System.out.println("**************************");
         //Lambda表达式的写法
            Runnable r2 = () -> System.out.println("公众号:程序员汪汪------关注我");
            r2.run();
    
        }
    }
    
  2. 情况二:Lambda表达式需要一个参数,但是没有返回值。

    //语法格式二:Lambda需要一个参数,但是没有返回值
        @Test
        public void test2() {
            //普通写法
            Consumer con = new Consumer() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };
            con.accept("公众号:程序员汪汪------关注我");
    
            System.out.println("**************************");
         //Lambda表达式的写法
            Consumer con1 = (String s) -> {System.out.println(s);};
            con1.accept("公众号:程序员汪汪------关注我");
    
        }
    
  3. 情况三:数据类型可以省略,因为可由编译器推断得出,称为类型推断

    //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
        @Test
        public void test3() {
            Consumer con1 = (String s) -> {
                System.out.println(s);
            };
            con.accept("公众号:程序员汪汪------关注我");
    
            System.out.println("**************************");
    
            //Lambda表达式的写法
            Consumer con1 = (String s) -> {System.out.println(s);};
            con1.accept("公众号:程序员汪汪------关注我");
        }
    
  4. 情况四:Lambda表达式若只需要一个参数时,参数的小括号可以省略

    //语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
        @Test
        public void test4() {
            Consumer con1 = (s) -> {
                System.out.println(s);
            };
            con1.accept("hhhhh");
    
            System.out.println("***************");
    
            Consumer con2 = s -> {
                System.out.println(s);
            };
            con2.accept("hhhhhhhhhhhh");
    
        }
    

    注意:无参数时,小括号是一定不能省略的。

  5. 情况五:Lambda表达式需要两个或两个以上的参数,有多条执行语句,并且可以有返回值

    //语法格式五:Lambda表达式需要两个或两个以上的参数,有多条执行语句,并且可以有返回值
        @Test
        public void test5() {
            Comparator com1 = new Comparator() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    System.out.println(o1);
                    System.out.println(o2);
                    return o1.compareTo(o2);
                }
            };
            System.out.println(com1.compare(12, 21));
            System.out.println("***********************");
    
            Comparator com2 = (o1, o2) -> {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            };
            System.out.println(com2.compare(12, 6));
        }
    
  6. 情况六:当Lambda体只有一条语句时,return 与大括号若有,都可以省略

    //语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
        @Test
        public void test6() {
            Comparator com1 = (o1, o2) -> {
                return o1.compareTo(o2);
            };
            System.out.println(com1.compare(12, 6));
    
            System.out.println("****************");
    
            Comparator com2 = (o1, o2) -> o1.compareTo(o2);
    
            System.out.println(com2.compare(12, 26));
    
        }
    

总结

->左边:Lambda表达式的形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,其一对()可以省略,没有参数和有多个参数的情况下()不能省略。

->右边:Lambda体应该使用一对{}包裹;如果Lambda体只有一条执行语句(可能是return语句),其一对{}可以省略,如果是return语句,想要省略一对{},那么return关键字也必须省略;有多条语句时,不能省略。

Lambda表达式的本质:作为函数式接口的实例。函数式接口就是只声明了一个抽象方法的接口。

函数式接口

函数式接口就是只声明了一个抽象方法的接口。

在Java 8中,java.util.function包下定义了丰富的函数式接口,这些接口上都使用了@FunctionalInterface注解,这样可以检查它是否是一个函数式接口。我们也可以使用该注解定义自己的函数式接口。

想要使用Lambda表达式,那么就一定需要函数式接口,Lambda就是函数式接口的实例。

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

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

其他接口

函数式接口 参数类型 返回类型 用途
BiFunction T, U R 对类型为T, U参数应用操作,返回R类型的结果。包含方法:R apply(T t, U u)
UnaryOperator
(Function子接口)
T T 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法:T apply(T t)
BinaryOperator
(BiFunction子接口)
T, T T 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法:T apply(T t1, T t2)
BiConsumer T, U void 对类型为T, U参数应用操作。包含方法:void accept(T t, U u)
BiPredicate T, U boolean 包含方法:boolean test(T t, U u)
ToIntFunction
ToLongFunction
ToDoubleFunction
T int
long
double
分别计算int、long、double值得函数
IntFunction
LongFunction
DoubleFunction
int
long
double
R 参数分别为int、long、double类型的函数

Java提供的函数式接口还有很多,上表只列举了一部分函数式接口,在以后的开发中可能会遇到。凡是开发中遇到了函数式接口,都可以运用Lambda表达式。

函数式接口的基本命名准则

Java提供的函数式接口遵循以下基本命名准则:

  1. 如果只处理对象而非基本类型,名称则为 FunctionConsumerPredicate 等。参数类型通过泛型添加。
  2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumerDoubleFunctionIntPredicate 等,但返回基本类型的 Supplier 接口例外。
  3. 如果返回值为基本类型,则用 To 表示,如 ToLongFunction IntToLongFunction
  4. 如果返回值类型与参数类型相同,则是一个 Operator :单个参数使用 UnaryOperator,两个参数使用 BinaryOperator
  5. 如果接收参数并返回一个布尔值,则是一个 断言 (Predicate)。
  6. 如果接收的两个参数类型不同,则名称中有一个 Bi

这里使用java内置的四大核心函数式接口举两个例子:

public class LambdaTest2 {
    //消费型接口Consumer
    public void happyTime(double money, Consumer consumer) {
        consumer.accept(money);
    }

    @Test
    public void test() {
        happyTime(500, new Consumer() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("学习好累啊,出去嗨一下,花了" + aDouble + "元");
            }
        });
        System.out.println("******************************");

        happyTime(400, money -> {
            System.out.println("学习好累啊,出去嗨一下,花了" + money + "元");
        });
    }

    //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
    public List filterString(List list, Predicate predicate) {
        ArrayList filterList = new ArrayList<>();
        for (String s : list) {
            if (predicate.test(s)) {
                filterList.add(s);
            }
        }

        return filterList;
    }

    //断定型接口 Predicate
    @Test
    public void test2(){
        List list = Arrays.asList("北京", "南京", "天津", "东京", "西京", "普京");
        //普通写法
        List filterList = filterString(list, new Predicate() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterList);

        System.out.println("******************************");
        //Lambda表达式写法
        List filterList2 = filterString(list, s -> s.contains("京"));
        System.out.println(filterList2);
    }

}
/**
test1 运行结果:
    学习好累啊,出去嗨一下,花了500.0元
    ******************************
    学习好累啊,出去嗨一下,花了400.0元
test2 运行结果:
    [北京, 南京, 东京, 西京, 普京]
    ******************************
    [北京, 南京, 东京, 西京, 普京]

*/

方法引用

方法引用本质上就是Lambda表达式,而Lambda是函数式接口的实例,所以方法引用也是函数式接口的实例

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

方法引用的格式:

类(或对象) :: 方法名

具体可以分为如下的三种情况:

  1. 对象 :: 非静态方法
  2. 类 :: 静态方法
  3. 类 :: 非静态方法

案例

Employee工具类:

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() {

    }

    public Employee(int id) {

        this.id = 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 MethodRefTest {

    // 情况一:对象 :: 实例方法
    //Consumer中的void accept(T t)
    //PrintStream中的void println(T t)
    @Test
    public void test1() {

        //普通写法
        Consumer con = new Consumer() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("hello!");
        System.out.println("*****************************");
        //Lambda表达式写法
        Consumer con1 = str -> System.out.println(str);
        con1.accept("北京");

        System.out.println("*****************************");
        //方法引用的写法
        PrintStream ps = System.out;
        Consumer con2 = ps::println;
        con2.accept("天安门");
        
        /**
        test1 运行结果:
            hello!
            *****************************
            北京
            *****************************
            天安门
        */

    }

    //Supplier中的T get()
    //Employee中的String getName()
    @Test
    public void test2() {
        //普通写法
        Employee emp = new Employee(1001, "Tom", 20, 5000);
        Supplier sup = new Supplier() {
            @Override
            public String get() {
                return emp.getName();
            }
        };
        System.out.println(sup.get());
        System.out.println("***************************************");
        //Lambda表达式写法,emp对象不变
        Supplier sup1 = () -> emp.getName();
        System.out.println(sup1.get());
        System.out.println("***************************************");
        //方法引用的写法
        Supplier sup2 = emp::getName;
        System.out.println(sup2.get());
        
        /**
        test2 运行结果:
            Tom
            ***************************************
            Tom
            ***************************************
            Tom
        */

    }

    // 情况二:类 :: 静态方法
    //Comparator中的int compare(T t1,T t2)
    //Integer中的int compare(T t1,T t2)
    @Test
    public void test3() {
        //普通写法
        Comparator com = new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(com.compare(12, 20));
        System.out.println("***************************************");
        //Lambda 表达式的写法
        Comparator com1 = (o1, o2) -> Integer.compare(o1, o2);
        System.out.println(com1.compare(12, 20));
        System.out.println("***************************************");
        //方法引用
        Comparator com2 = Integer::compare;
        System.out.println(com2.compare(12, 20));
        /**
        test3 运行结果:
            -1
            ***************************************
            -1
            ***************************************
            -1
        */
    }

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    @Test
    public void test4() {
        //普通写法
        Function fun = new Function() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        System.out.println(fun.apply(12.3));
        System.out.println("***************************************");
        //Lambda表达式写法
        Function fun1 = (aDouble) -> Math.round(aDouble);
        System.out.println(fun1.apply(12.6));
        System.out.println("***************************************");
        //方法引用的写法
        Function fun2 = Math::round;
        System.out.println(fun2.apply(12.5));
        /**
        test4 运行结果:
            12
            ***************************************
            13
            ***************************************
            13
        */

    }

    // 情况三:类 :: 实例方法
    // Comparator中的int compare(T t1,T t2)
    // String中的int t1.compareTo(t2)
    @Test
    public void test5() {
        //普通方法
        Comparator com = new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        };
        System.out.println(com.compare("abc", "abe"));
        System.out.println("***************************************");
        //Lambda 表达式的方式
        Comparator com1 = (o1, o2) -> o1.compareTo(o2);
        System.out.println(com1.compare("abc", "abe"));
        System.out.println("***************************************");
        //方法引用的方式
        Comparator com2 = String::compareTo;
        System.out.println(com2.compare("abc", "abe"));
        /**
        test5 运行结果:
            -2
            ***************************************
            -2
            ***************************************
            -2
        */

    }

    //BiPredicate中的boolean test(T t1, T t2);
    //String中的boolean t1.equals(t2)
    @Test
    public void test6() {
        //普通写法
        BiPredicate bip = new BiPredicate() {
            @Override
            public boolean test(String s, String s2) {
                return s.equals(s2);
            }
        };
        System.out.println(bip.test("abc", "abd"));
        System.out.println("***************************************");
        //Lambda表达式写法
        BiPredicate bip1 = (s1, s2) -> s1.equals(s2);
        System.out.println(bip1.test("abc", "abc"));
        System.out.println("***************************************");
        //方法引用
        BiPredicate bip2 = String::equals;
        System.out.println(bip2.test("abc", "abc"));
        
        /**
        test6 运行结果:
            false
            ***************************************
            true
            ***************************************
            true
        */

    }

    // Function中的R apply(T t)
    // Employee中的String getName();
    @Test
    public void test7() {
        //普通写法
        Employee emp = new Employee(1001, "Tom", 20, 5000);
        Function func = new Function() {
            @Override
            public String apply(Employee employee) {
                return employee.getName();
            }
        };
        System.out.println(func.apply(emp));
        System.out.println("***************************************");
        //Lambda 表达式的写法 emp不变
        Function func1 = employee -> employee.getName();
        System.out.println(func1.apply(emp));
        System.out.println("***************************************");
        //方法引用写法
        Function func2 = Employee::getName;
        System.out.println(func2.apply(emp));
        /**
        test7 运行结果:
            Tom
            ***************************************
            Tom
            ***************************************
            Tom
        */
    }
    

}

方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同,如果是成员方法(非静态方法),会在方法的参数列表的第一个位置增加this的类型。

分析

首先我们来分析一下test1的例子ps::println,在本例中,实际上调用的是void println(String str),它是非静态方法,也就是成员方法,按照上述的要求,需要在参数列表的第一个位置补上this也就是PrintStream,参数列表就变成了这样void println(PrintStream ps String str),但是因为::前是一个PrintStream实例ps,因此,方法引用的第一个参数this被绑定给了ps,所以最终的参数列表又变回了void println(String str),所以就符合了要求中所说的参数列表和返回值类型都与方法引用的方法的形参列表和返回值类型相同的要求,那么就和Consumer接口的抽象方法void accept(T t)的返回值和参数列表一致了,所以就可以使用方法引用。

再来看test3的例子,test3是情况2的例子,调用的是静态方法,静态方法没有this,所以只要满足参数列表和返回值相同的条件就可以使用方法引用。

最后看test5的例子String::compareTo,在这个例子中,int compareTo(String str)是一个成员方法(非静态方法),按照上述的方法引用使用的要求,需要在参数列表的第一个位置上补上this(在这里,this的类型就是String),为什么是String?因为compareToString类的成员方法,this当然就是指的是String。添加this后,新的参数列表就变成了int compareTo(String s, String str),此时就符合Comparator接口中的int compare(T t1,T t2)的要求,所以方法引用就能够使用了。

总结

  1. 成员方法(非静态方法)的参数列表,前面会追加this的类型。
  2. 静态方法因为没有this,所以参数列表不会追加任何东西
  3. ::前是一个实例时,本应追加到参数列表中的this会绑定给这个实例。

构造器引用与数组引用

构造器引用:构造器引用和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型。

数组引用:把数组看成是一个特殊的类,则写法就与构造器引用一致了。

案例:

案例中仍需使用上面的Employee工具类。

public class ConstructorRefTest {
    //构造器引用
    //Supplier中的T get()
    @Test
    public void test1(){
        //普通写法
        Supplier sup = new Supplier() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println(sup.get());
        System.out.println("***************************************");
        //Lambda 表达式写法
        Supplier sup1 = () -> new Employee();
        System.out.println(sup1.get());
        System.out.println("***************************************");
        //构造器引用写法
        Supplier sup2 = Employee::new;
        System.out.println(sup2.get());
        
        /**
        运行结果:
            Employee{id=0, name='null', age=0, salary=0.0}
            ***************************************
            Employee{id=0, name='null', age=0, salary=0.0}
            ***************************************
            Employee{id=0, name='null', age=0, salary=0.0}
        */
    }

    //Function中的R apply(T t)
    @Test
    public void test2(){
        //普通写法
        Function func = new Function() {
            @Override
            public Employee apply(Integer id) {
                return new Employee(id);
            }
        };
        System.out.println(func.apply(1001));
        System.out.println("***************************************");
        // Lambda
        Function func1 = id -> new Employee(id);
        System.out.println(func1.apply(1002));
        System.out.println("***************************************");
        //构造器引用
        Function func2 = Employee::new;
        System.out.println(func2.apply(1003));
        
        /**
        运行结果:
            Employee{id=1001, name='null', age=0, salary=0.0}
            ***************************************
            Employee{id=1002, name='null', age=0, salary=0.0}
            ***************************************
            Employee{id=1003, name='null', age=0, salary=0.0}
        */

    }

    //BiFunction中的R apply(T t,U u)
    @Test
    public void test3(){
        //普通写法
        BiFunction func = new BiFunction() {
            @Override
            public Employee apply(Integer id, String name) {
                return new Employee(id, name);
            }
        };
        System.out.println(func.apply(1001, "Tom"));
        System.out.println("***************************************");
        //Lambda
        BiFunction func1 = (id, name) -> new Employee(id, name);
        System.out.println(func1.apply(1002, "Jerry"));
        System.out.println("***************************************");
        //构造器引用
        BiFunction func2 = Employee::new;
        System.out.println(func2.apply(1003, "Jack"));
        
        /**
        运行结果:
            Employee{id=1001, name='Tom', age=0, salary=0.0}
            ***************************************
            Employee{id=1002, name='Jerry', age=0, salary=0.0}
            ***************************************
            Employee{id=1003, name='Jack', age=0, salary=0.0}
        */

    }

    //数组引用
    //Function中的R apply(T t)
    @Test
    public void test4(){
        //普通写法  Integer代表数组长度
        Function func1 = new Function() {
            @Override
            public String[] apply(Integer length) {
                return new String[length];
            }
        };
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));
        System.out.println("***************************************");
        //Lambda
        Function func2 = length -> new String[length];
        String[] arr2 = func2.apply(8);
        System.out.println(Arrays.toString(arr2));
        System.out.println("***************************************");
        //数组引用
        Function func3 = String[]::new;
        String[] arr3 = func3.apply(10);
        System.out.println(Arrays.toString(arr3));
        
        /**
        运行结果:
            [null, null, null, null, null]
            ***************************************
            [null, null, null, null, null, null, null, null]
            ***************************************
            [null, null, null, null, null, null, null, null, null, null]
        */

    }
    
}

结语:

Lambda表达式,还是需要多练多写,如果实在不会写,可以使用IDEA的提示功能,在可以使用Lambda表达式的地方,可以通过提示,自动转换成Lambda表达式。建议还是自己多写多练。

你可能感兴趣的:(Lambda表达式详解)