Lambda表达式

1、Lamdba表达式

1.1、Lambda表达式的概念

Lambda表达式,从本质上来讲,是一个匿名函数。可以使用这个匿名函数,实现接口中的方法,从而简化代码。

1.2、Lambda表达式的使用场景

通常来讲,使用Lambda表达式,是为了简化接口实现的。

关于接口实现,可以有很多种方式来实现。例如:设计借口的实现类、使用匿名内部类。但是lambda表达式,比这两种方式简单。

package com.helen.lambda.main;

import com.helen.lambda.funcationInterface.SingleReturnSingleParameter;

/**
 * @Description
 * @Author BF <xu.rongchang@foxmail.com>
 * @Version 1.0
 * @Date 2021/6/3
 */
public class Program {
    public static void main(String[] args) {
        interfaceImpl();
    }


    public static void interfaceImpl(){
        // 1、使用显示的实现类对象
        SingleReturnSingleParameterImpl parameter1 = new SingleReturnSingleParameterImpl();
        // 2、使用匿名内部类的方式
        SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter() {
            @Override
            public int test(int a) {
                return a * a;
            }
        };
        // 3、使用lambda表达式的方式实现
        SingleReturnSingleParameter parameter3 = a ->  a * a;
     
        System.out.printf("显示的实现类对象: %d
",parameter1.test(10));
        System.out.printf("匿名内部类的方式: %d
", parameter2.test(10));
        System.out.printf("lambda表达式的方式: %d
", parameter3.test(10));
    }
}


class SingleReturnSingleParameterImpl implements SingleReturnSingleParameter{
    @Override
    public int test(int a) {
        return a * a;
    }
}

1.3、Lamdba表达式对接口的要求

虽然说,lambda表达式可以在一定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简洁实现的。

lambda表达式毕竟只是一个匿名方法。当实现的接口中的方法过多或者过少的时候(一个都没有),lambda表达式都是不适用的。

lambda表达式,只能实现函数式接口。

1.4、函数式接口

1.4.1、基础概念

如果说,一个接口中,要求实现类必须实现的抽象方法,有且只有一个! 这样的接口,就是函数式接口。

// 这个接口中,有且只有一个方法,是实现类必须实现的,因此是一个函数式接口。
interface Test1 {
     void test();
}

// 这个接口中,实现类必须实现的方法,有两个! 因此不是一个函数式接口。
interface Test2 {
    void test1();
    void test2();
}

// 这个接口中,实现类必须实现的方法,有零个!因此不是一个函数式接口。
interface Test3 {

}

// 这个接口中,虽然没有定义任何方法,但是可以从父类中继承一个抽象方法,因此是一个函数式接口。
interface Test4 extends Test1 {

}

// 这个接口,虽然里面定义了两个方法,但是 default 修饰的方法子类不是必须实现的。
// 因此子类必须实现的方法也只有一个,所以此接口是一个函数式接口。
interface Test5 {
    void test5();
    default void test2(){};
}

// 这个接口中的 toString 方法,是Object中定义的方法
// 此时实现类在实现接口的时候,toString可以不重写!因为可以从父类Object中继承到!
// 此时,实现类在实现接口的时候,有且只有一个接口需要实现,因此是一个函数式接口。
interface Test6 {
    void test6();
    String toString();
}

1.4.2、@FunctionalInterface

是一个注解,用在接口上,判断这个接口是否是一个函数式接口。如果是函数式接口,没任何问题。如果不是一个函数式接口,则会报错。功能类似于 @Override。

@FunctionalInterface
interface Test1 {
     void test();
}

2、Lambda表达式的语法

2.1、Lambda表达式的基础语法

Lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。

实际上,我们在写lambda表达式的时候,也不需要关系返回值的类型。

我们在写lambda表达式的时候,只需要关注两部分内容即可:参数列表方法体

lambda表达式的基础语法:

(参数) -> {
	方法体
};

参数部分:方法的参数列表,要求和实现的接口中的方法参数一致,包括参数的数量和类型。

方法体部分:方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。

-> : 分隔参数部分和方法体部分。

 public class BasicSyntax {
    public static void main(String[] args) {
        // 1、实现无参、无返回值函数式接口
        NoneReturnNoneParameter lambda1 = () -> {
            System.out.println("lambda 实现 无参、无返回值函数式接口");
        };
        lambda1.test();

        // 2、实现一个参数、无返回值的函数式接口
        NoneReturnSingleParameter lambda2 =  (int a) -> {
            System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a);
        };
        lambda2.test(100);

        // 3、实现多个参数、无返回值的函数式接口
        NoneReturnMultipleParameter lambda3 = (int a ,int b) -> {
            System.out.println("lambda 实现 多个参数、无返回值的函数式接口 参数a的值为:"+a+"    参数b的值为:"+b);
        };
        lambda3.test(100,200);

        // 4、实现有返回值、无参数的函数式接口
        SingleReturnNoneParameter lambda4 = () -> {
            System.out.println("lambda 实现 有返回值、无参数的函数式接口    返回值是:"+10);
            return 10;
        };
        int ret1 = lambda4.test();
        System.out.println("ret1 = " + ret1);

        // 5、实现有返回值、一个参数的函数式接口
        SingleReturnSingleParameter lambda5 = (int a) -> {
            System.out.println("lambda 实现 有返回值、一个参数的函数式接口    返回值是:"+a);
            return a;
        };
        int ret2 = lambda5.test(1998);
        System.out.println("ret2 = " + ret2);
        
        // 6、实现有返回值、多个参数的函数式接口
        SingleReturnMultipleParameter lambda6 = (int a ,int b) -> {
            System.out.println("lambda 实现 有返回值、多个参数的函数式接口");
            return a+b;
        };
        int ret3 = lambda6.test(100,200);
        System.out.println("ret3 = " + ret3);
    }
}

2.2、Lambda表达式的语法进阶

在上述代码中,的确可以使用lambda表达式实现接口,但是依然不够简洁,有简化的空间。

2.2.1、参数部分的精简

  • 参数的类型

    • 由于在接口的方法中,已经定义了每一个参数的类型是什么。而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类型需要和接口中的方法保持一致。因此,此时lambda表达式中的参数类型可以省略不写。

    • 注意事项:

      • 如果需要省略参数的类型,要保证:要省略,每一个参数的类型必须保证不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。

        // 3、实现多个参数、无返回值的函数式接口
                NoneReturnMultipleParameter lambda3 = ( a , b) -> {
                    System.out.println("lambda 实现 多个参数、无返回值的函数式接口 参数a的值为:"+a+"    参数b的值为:"+b);
                };
                lambda3.test(100,200);
        
  • 参数的小括号

    • 如果方法的参数列表中参数数量 有且只有一个 ,此时,参数列表的小括号是可以省略不写的。

    • 注意事项:

      • 只有当参数的数量是一个的时候,才可以省略不写。

      • 省略小括号的同时,必须要省略参数的类型。

        // 2、实现一个参数、无返回值的函数式接口
                NoneReturnSingleParameter lambda2 =   a -> {
                    System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a);
                };
                lambda2.test(100);
        

2.2.2、方法体部分

  • 方法体的大括号

    • 如果方法体有且只有一条语句时,方法体的大括号可以省略不写。

      // 2、实现一个参数、无返回值的函数式接口
              NoneReturnSingleParameter lambda2 =   a -> System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a);
              lambda2.test(100);
      
  • 返回关键字return

    • 如果方法体有且只有一条语句,并且就是返回语句时。return 关键字可以省略不写。

      //之前:
      // 6、实现有返回值、多个参数的函数式接口
              SingleReturnMultipleParameter lambda6 = (int a ,int b) -> {
                  return a * b;
              };
              int ret3 = lambda6.test(100,200);
              System.out.println("ret3 = " + ret3);
      
      // 精简之后:
      // 6、实现有返回值、多个参数的函数式接口
              SingleReturnMultipleParameter lambda6 = (int a ,int b) -> a * b;
              int ret3 = lambda6.test(100,200);
              System.out.println("ret3 = " + ret3);
      

3、函数引用

lambda表达式是为了简化接口实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,一般会单独的写一个方法。在lambda表达式中直接引用这个方法即可。

或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。

函数引用:引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。

3.1、静态方法的引用

  • 语法:

    • 类::静态方法
  • 注意事项:

    • 在引用的方法后面,不要添加小括号。
    • 引用的这个方法,参数(数量、类型) 和 返回值,必须要跟接口定义的一致。
  • 示例:

    /**
     * @Description  函数引用 ---> 静态方法的引用
     * @Author BF <xu.rongchang@foxmail.com>
     * @Version 1.0
     * @Date 2021/6/3
     */
    public class Syntax1 {
    
        private interface Calculator{
            int cacl(int a ,int b);
        }
        public static void main(String[] args) {
            Calculator calculator = Syntax1::cal;
            System.out.println(calculator.cacl(10,15));
        }
    
        public static int cal(int a ,int b){
            if (a>b){
                return a - b;
            }else if (b>a){
                return b - a;
            }
            return a + b ;
        }
    }
    

3.2、非静态方法的引用

  • 语法:

    • 对象::非静态方法
  • 注意事项:

    • 在引用的方法后面,不要添加小括号。
    • 引用的这个方法,参数(数量、类型)和 返回值,必须要跟接口中定义的一致。
  • 示例:

    /**
     * @Description  函数引用 ---> 非静态方法的引用
     * @Author BF <xu.rongchang@foxmail.com>
     * @Version 1.0
     * @Date 2021/6/3
     */
    public class Syntax1 {
    
        private interface Calculator{
            int cacl(int a ,int b);
        }
        public static void main(String[] args) {
            Calculator calculator = new Syntax1()::cal;
            System.out.println(calculator.cacl(10,15));
        }
    
        public  int cal(int a ,int b){
            if (a>b){
                return a - b;
            }else if (b>a){
                return b - a;
            }
            return a + b ;
        }
    }
    

3.3、构造方法的引用

  • 使用场景:

    • 如果某一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
  • 语法:

    • 类名::new
  • 注意事项:

    • 可以通过接口中的方法的参数,区分引用不同的构造方法。
  • 示例:

    /**
     * @Description  函数引用 ---》 构造方法的引用
     * @Author BF <xu.rongchang@foxmail.com>
     * @Version 1.0
     * @Date 2021/6/3
     */
    public class Lambda1 {
    
        private static class Person{
            public Person() {
                System.out.println("Person类的无参构造执行了.....");
            }
            public Person(String str) {
                System.out.println("Person类的一个参数的构造方法执行了.....");
            }
            public Person(String str,int age) {
                System.out.println("Person类的多个参数的构造方法执行了.....");
            }
        }
        @FunctionalInterface
        private interface GetPersonWithNoneParameter{
            Person get();
        }
        @FunctionalInterface
        private interface GetPersonWithSingleParameter{
            Person get(String str);
        }
        @FunctionalInterface
        private interface GetPersonWithMultipleParameter{
            Person get(String str,int age);
        }
    
        public static void main(String[] args) {
            // 1、通过 lambda 表达式,实现了 GetPersonWithNoneParameter 接口
            GetPersonWithNoneParameter person1 = Person::new;
            // 2、通过 lambda 表达式,实现了 GetPersonWithSingleParameter 接口
            GetPersonWithSingleParameter person2 = Person::new;
            // 3、通过 lambda 表达式,实现了 GetPersonWithMultipleParameter 接口
            GetPersonWithMultipleParameter person3 = Person::new;
    
            person1.get();
            person2.get("111");
            person3.get("123",123);
        }
    }
    

3.4、对象方法的特殊引用

如果在使用lambda表达式,实现某些接口的时候。lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。其它的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。

/**
 * @Description 函数引用 ---》 对象方法的特殊引用(非静态方法)
 * @Author BF <xu.rongchang@foxmail.com>
 * @Version 1.0
 * @Date 2021/6/3
 */
public class Lambda2 {

    private static class Person{
        private String name;

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

        public String getName() {
            return name;
        }

        public void show(){

        }
        public int showTest(int a ,int b){
                return a + b;
        }
    }

    @FunctionalInterface
    private interface GetName{
        String getName01(Person person);
    }

    @FunctionalInterface
    private interface SetName{
        void setName02(Person person,String name);
    }

    @FunctionalInterface
    private interface VoidName{
        void voidName02(Person person);
    }

    @FunctionalInterface
    private interface AddShow{
        void addShow1(Person person,int a,int b);
    }


    public static void main(String[] args) {

        GetName getName1 = Person -> Person.getName();
        /**
         * lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。
         * 此时,可以对这种实现进行简化。
         * */
        GetName getName2 = Person::getName;


        SetName setName3 = (Person,name) -> Person.setName(name);
        /**
         * lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。
         * 其它的参数,可以作为调用方法的参数。
         * 此时,可以对这种实现进行简化。
         * */
        SetName setName4 = Person::setName;



        /**
         * 对于这个方法的实现逻辑,刚好是调用参数对象的某一个方法
        **/

        VoidName voidName5 = Person -> Person.show();
        VoidName voidName6 = Person::show;


        /**
         * 对于这个方法的实现逻辑,刚好是调用参数对象的某一个方法
         * 带参数了和上面差不多
         **/
        AddShow addShow7 = (Person,a,b) -> Person.showTest(a, b);
        AddShow addShow8 = Person::showTest;

    }
}

4 、Lambda表达式需要注意的地方

/**
 * @Description  Lambda表达式需要注意的地方
 * @Author BF <xu.rongchang@foxmail.com>
 * @Version 1.0
 * @Date 2021/6/3
 */
public class Lambda3 {

        //static int x = 100;
    public static void main(String[] args) {
        // 1、定义一个局部变量
        int x = 100;
        // 2、使用Lambda表达式实现接口
        AddTest addTest = () -> System.out.println("X的值为:"+x);
        // 3、修改变量的值
        int x = 50;

        /**
         * 发现 x 的值,无法被修改;
         * 原因在于 局部变量x传递进 Lambda表达式后形成包围(闭包)了。而且 x 的修饰符将增加为 final
         * 导致下一行代码无法进行修改。
         * 解决:我们将局部变量升级为全局变量 ,即可解决为题!
         * */
    }
}

@FunctionalInterface
interface AddTest{
    void test();
}
原文地址:https://www.cnblogs.com/whitespaces/p/14847600.html