Java编程--函数式接口/lambda表达式/变量捕获

       前言 

        逆水行舟,不进则退!!!     


目录

       函数式接口与lambda表达式

       变量捕获 

       补充


       函数式接口与lambda表达式

函数式接口:一个接口有且只有一个抽象方法。

注意:

        1,如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口

        2,@FunctionalInterface   注解 加在接口前,那么编译器就会按照函数式接口的定义来要求该接口,如果有两个及以上的抽象方法,编译程序就会报错。这个注解可以不加,加上是会自动进行检测

        形如: 

@FunctionalInterface
interface NoParameterNoReturn {
    //只有这一个抽象方法
    void test();
}

        注意,函数式接口限制的是:有且只能有一个抽象方法,也就意味着函数式接口也可以有静态方法,普通方法 。如下:

@FunctionalInterface
    interface NoParameterNoReturn {
        //只有这一个抽象方法
        void test();
        
        //静态方法
        static void test1() {
            
        }
        // 普通方法
        default void test2() {
            
        }
    }

        

        在不使用lambda表达式时,我们实现一个接口是通过匿名内部类的方式来完成。如下:

//抽象方法无返回值无参数 的函数式接口
@FunctionalInterface
    interface NoParameterNoReturn {
        void test();
    }

public static void main(String[] args) {

        NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
            @Override
            public void test() {
                System.out.println("hello");
            }
        };
        noParameterNoReturn.test();
    }

        解释代码:我们创建了一个匿名子类 new NoParameterNoReturn() { },  它继承自NoParameterNoReturn 这个接口,重写了其中的 test方法,然后我们将这个匿名子类的实例赋值给了变量 noParameterNoReturn。通过这个变量,可以调用重写的方法。

        接下来我们使用lambda表达式来实现几个接口:

1,无返回值无参数
//无返回值无参数
    @FunctionalInterface
    interface NoParameterNoReturn {
        //无返回值无参数
        void test();
    }
public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = () -> System.out.println("hello");
        noParameterNoReturn.test();
        
    }

2,无返回值一个参数
//无返回值一个参数
    @FunctionalInterface
    interface OneParameterNoReturn {
        //无返回值一个参数
        void test(int a);
    }

    public static void main(String[] args) {
        OneParameterNoReturn oneParameterNoReturn = (int a) -> System.out.println(a);
        oneParameterNoReturn.test(14);
    }

3,无返回值多个参数
//无返回值多个参数
    @FunctionalInterface
    interface MoreParameterNoReturn {
        //无返回值多个参数
        void test(int a,int b);
    }

    public static void main(String[] args) {
        MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> System.out.println(a+b);
        moreParameterNoReturn.test(3,3);
    }

4,有返回无参数
//有返回值无参数
    @FunctionalInterface
    interface NoParameterReturn {
        //有返回值无参数
        int test();
    }

    public static void main(String[] args) {
        NoParameterReturn noParameterReturn = () -> {return 10;};
        int ret = noParameterReturn.test();
        System.out.println(ret);
    }

5,有返回值一个参数
//有返回值一个参数
    @FunctionalInterface
    interface OneParameterReturn {
        //有返回值一个参数
        int test(int a);
    }

    public static void main(String[] args) {
        OneParameterReturn oneParameterReturn = (a) -> {return a+10;};
        int ret = oneParameterReturn.test(4);
        System.out.println(ret);
    }

6,有返回值多个参数

    //有返回值多参数
    @FunctionalInterface
    interface MoreParameterReturn {
        // 有返回值多个参数
        int test(int a,int b);
    }

    public static void main(String[] args) {
        MoreParameterReturn moreParameterReturn = (a,b) -> {return a+b;};
        int ret = moreParameterReturn.test(4,5);
        System.out.println(ret);
    }

总结:

  1. Lambda表达式语法精简:
    1. 参数类型不可以单独省略,如果需要省略,每个参数的类型都要省略。
    2. 参数的小括号里只有一个参数,那么小括号可以省略
    3. 如果方法体当中只有一句代码,那么大括号可以省略
    4. 如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且return也可以省略。

        我的理解:

() -> {

};

        圆括号中是所需要的参数, “->” 表示的是 这里是一个 lambda表达式, 花括号 中是函数体     


       变量捕获 

        1,lambda表达式可以捕获外部变量   

                我的理解: lambda可以访问到lambda表达式所在的上下文环境中的变量,当Lambda表达式捕获外部变量时,它会在创建的时候将指定的变量复制一份,并在函数体中使用这份副本。尽管lambda表达式可以捕获外部变量,但在lambda表达式内部对这些捕获的变量进行的修改不会影响到原变量的值。

                在Java中,lambda表达式的捕获方式 主要分为 值捕获 和 引用捕获。 值捕获主要是针对 外部的局部变量引用捕获 只要针对的是外部的 实例变量 和 静态变量

                在lambda表达式中,值捕获和引用捕获是两种不同的变量捕获方式。值捕获是将变量的值拷贝到lambda表达式中,这意味着在lambda表达式中使用的是原始变量的副本,对副本的修改不会影响到原始变量。而引用捕获则是将变量的引用拷贝到lambda表达式中,这意味着在lambda表达式中使用的是原始变量的引用,对引用所指向的对象做的修改将影响到原始变量。

                需要注意的是,在使用引用捕获时,要确保被引用的对象在 lambda 表达式执行时仍然存在。因为引用捕获传递的是引用,如果后面该对象被销毁了,再通过这个引用去访问就会产生未定义的行为。而值捕获传递的是值的副本,所以在之后对该变量做的修改不会影响到当前已经传入的值。

                同时,关于lambda表达式中的参数和被捕获的变量,需要特别理解的是:lambda表达式的“参数”是在被调用时拷贝,而“被捕获的变量的值”是在lambda表达式创建时拷贝。

                对于局部变量,Java要求必须是final或者是事实上的final(即在初始化后不会再发生变化)。如果局部变量符合这些条件,那么它们就可以被Lambda表达式捕获并在其中使用。捕获的局部变量在lambda表达式中使用时,不能去修改。

                另外,需要注意的是,虽然Lambda表达式可以捕获实例变量,但这并不是通过值传递的方式实现的。实际上,Lambda表达式内部只能访问到实例变量的getter方法,而无法直接访问到实例变量的值。这是为了防止潜在的并发问题和数据不一致情况。

                实例变量也被称为对象变量,它是定义在类中,但在方法体(包括构造方法)之外的变量。当基于某个类创建对象时,每个对象都有自己独立的实例变量副本,互不干扰,可以通过对象的引用来访问这些实例变量。

                类成员变量是属于类的,所有对象共享同一个类成员变量,在使用时可以通过类名来访问。被static修饰

                静态变量是属于类的,所有对象共享同一个静态变量,在使用时可以直接通过类名来访问。需要注意的是,静态变量只能被赋值一次,并且它的值在整个程序运行期间都是不变的。被static 和 final 修饰


 

以上呢,都是我通过查找资料找到的,但是呢,当我问在IT行业工作多年的大佬的时候,对方是这样说的:

       “ lambda表达式捕获变量,Java要求必须是final或者是事实上的final(即在初始化后不会再发生变化)。如果变量符合这些条件,那么它们就可以被Lambda表达式捕获并在其中使用。捕获的局部变量在lambda表达式中使用时,不能去修改。”

       好像并没有强调值捕获引用捕获之类的,只说了只一点,那我也没办法,不好取舍,只好将这些东西都写在这,或许会有用。

 


如何确定lambda表达式中获取到的this是指向谁?

       答:在Java中,Lambda表达式中的this指向的是创建该Lambda表达式的方法中的this。具体来说,当一个对象调用一个方法,该方法返回一个Lambda表达式时,这个Lambda表达式会捕获这个方法的this引用。因此,如果在Lambda表达式中使用了this关键字,那么它指向的就是这个方法的this引用。

       此外,需要注意的是,虽然Lambda表达式看起来和一个内部匿名类很相似,但二者有一个主要的区别:在内部匿名类中,this关键字指向的是匿名类本身,而在Lambda表达式中,this关键字指向的是当前类。

什么是捕获列表?

        答:捕获列表是C++中lambda表达式的一个特性,它用于定义一个lambda所在函数中局部变量的列表。在默认情况下,lambda表达式内部是不能直接使用外部的变量的,此时,捕获列表就可以起到传递外部数据的作用。捕获列表通常为空,表示lambda不使用它所在函数中的任何局部变量。此外,根据传递的行为,捕获列表也可以分为值捕获引用捕获两种类型。值捕获 和 引用捕获 不能出现在同一个 捕获列表。总的来说,捕获列表是lambda表达式的一个重要组成部分,它使得lambda能够访问并使用其所在函数的局部变量。


       补充

        1,注意区分 lambda表达式 和 匿名内部类: 

                Lambda表达式和匿名内部类在Java中都用于简化代码书写,但二者具有显著的不同。匿名内部类是一个继承自具体类或抽象类的子类,它可以用来创建任何类型的接口的实例,而Lambda表达式则是用来创建函数式接口的实例。

               它们的主要区别还体现在以下几点:首先,所需的类型不同。匿名内部类可以是接口、抽象类或具体类的子类,而Lambda表达式只是针对函数式接口其次,使用限制也存在差异。如果接口中仅有一个抽象方法,既可以使用Lambda表达式,也可以使用匿名内部类。但是,如果接口中有多个抽象方法,则只能使用匿名内部类。

                此外,Java Lambda表达式的一个重要用途是简化某些匿名内部类的写法。例如,我们可以通过下列两种方式来创建并启动一个新线程:

// 使用匿名内部类的方式
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread using anonymous inner class");
    }
}).start();

// 使用Lambda表达式的方式
new Thread(() -> System.out.println("Thread using lambda expression")).start();


                对比上述两种方法,可以发现Lambda表达式的写法更加简洁。

        2,lambda表达式的优缺点: 

                优点:

                        1,lambda表达式可以简化代码量,代码更精简,减少了冗余;

                        2,减少了匿名内部类的创建,每个匿名内部类都会生成一个 .class 文件,而lambda表达式则不会。所以lambda表达式也是节省了资源。

                

                缺点:

                        1,不利于后期维护。lambda表达式是匿名的,可读性差,对与维护工作来说,增加了不少难度。

                        2,学习成本更高,lambda表达式不容易理解,相比匿名内部类来说,需要更多的时间去学习。

                        


        我是专注学习的章鱼哥~

你可能感兴趣的:(Java多线程编程,心得,java,python,开发语言)