Java lambda表达式

一、简介

1. lambda表达式格式

(参数类型 参数名称) -> { 代码语句 }

说明:
(1) 小括号内:没有参数就留空(); 多个参数就用逗号分隔。
(2) -> 是新引入的语法格式,代表指向动作。
(3) 大括号内的语法与传统方法体要求基本一致。
(4) 参数类型可以省略,当编译器无法自动推导可以加上。

可省略部分:
凡是可以根据上下文推导得知的信息,都可以省略:
(1) 小括号内参数的类型可以省略;
(2) 如果小括号内只有一个参数,则小括号可以省略;
(3) 如果大括号内只有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。

2. 使用要求

(1) 使用Lambda表达式必须具有接口,且要求接口中有且仅有一个抽象方法。
(2) 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

3. 将Lambda匹配到接口规则

有且仅有一个抽象方法的接口称为“函数式接口”。将Java lambda表达式与函数式接口进行匹配需要步骤:

(1) 接口是否只有一个抽象方法? //必须 注:其它非抽象方法还是可以有的(但是实测是不能有的)
(2) lambda表达式的参数是否与抽象方法的参数匹配? //必须
(3) lambda表达式的返回类型是否与抽象方法的返回类型匹配? //必须

在lambda表达式中,通常可以推断参数类型,比如参数传lambda表达式,参数类型做为推断依据。lambda表达式内参数类型、个数也必须要完全匹配。


二、基础版本

1. 例子

(1) 普通实现——非lambda表达式

interface Learn {
    void study();
}

class StudyDemo implements Learn {
    @Override    
    public void study() {    
        System.out.println("Hello World");        
    }
}

public class Test {
    public static void main(String args[]) {
        Learn s = new StudyDemo();        
        s.study();
    }
}


(2) 匿名接口类实现——非lambda表达式

interface Learn {
    void study();
}

public class Test {
    public static void main(String args[]) {
        Learn s = new Learn() {
            @Override   
            public void study() { //复写不能导致acces权限变弱,必须加public
                System.out.println("Hello World");        
            }
        };
        s.study();
    }
}


(3) lambda表达式实现

interface Learn {
    void study();
}

public class Test {
    public static void main(String args[]) {
        /* 只有一行,等效:Learn s = () -> System.out.println("Hello World"); */
        Learn s = () -> {
            System.out.println("Hello World");
        };
        s.study();
    }
}


(4) 匿名接口类实现——有参数和返回值

interface Learn {
    void study(int a, int b);
}

public class Test {
    public static void main(String args[]) {
        Learn s = new Learn() {
            @Override    
            public void study(int a, int b) {    
                System.out.println("Hello World" + (a + b));        
            }
        };
        s.study(1, 2);
    }
}


(5) lambda表达式实现——有参数和返回值

interface Learn {
    void study(int a, int b);
}

public class Test {
    public static void main(String args[]) {
        Learn s = (a, b) -> { 
            System.out.println("Hello World x " + (a + b));
        };
        s.study(1, 2);
    }
}

//-----------------------

interface Learn {
    void study(int a);
}

public class Test {
    public static void main(String args[]) {
        //只有一个参数省略小括号,只有一行代码省略大括号
        Learn s = a -> System.out.println("Hello World x " + a);
        s.study(1);
    }
}


(6) lambda表达式实现——有返回值

interface Learn {
    int study(int a, int b);
}

public class Test {
    public static void main(String args[]) {
        Learn s = (a, b) -> {
            int c = a + b;
            System.out.println("Hello World x " + c);
            return c;
        };
        int ret = s.study(1, 2);
        System.out.println("ret= " + ret);
    }
}


4. 实现匿名接口可以增加一个成员属性,但是lambda不可以,lambda是无状态的。

interface Learn {
    int study(int a, int b);
}

public class Test {
    public static void main(String args[]) {
        Learn s = new Learn() {
            private int value = 0;
            @Override
            public int study(int a, int b) {
                int c = a + b;
                System.out.println("Hello World x " + c);
                //lambda表达式不行,因此lambda是无状态的
                value_inc(); //不能在外通过对象调用,但是在函数内部调用,维护内部状态
                return c;    
            }
            public void value_inc() {
                System.out.println("value=" + ++value);
            }
        };

        s.study(1, 2);
        s.study(1, 2);
        s.study(1, 2);
    }
}

从 Java 8 开始,lambda 是迄今为止表示小函数对象的最佳方式。 除非必须创建非函数式接口类型的实例,否则不要使用匿名类作为函数对象。


三、变量捕获

在某些情况下,Java lambda表达式能够访问在lambda表达式主体外部声明的变量。可以捕获以下变量类型:

(1) 局部变量
(2) 实例变量
(3) 静态变量

这些变量捕获的每一个将在以下各节中进行描述。

1. 局部变量捕获

//lambda表达式访问局部变量
interface Factory {
    public String create(char[] chars);
}

public class Test {
    public static void main(String args[]) {
        String str = "Hello World";
        Factory f = (chars) -> {
            return str + ":" + new String(chars); //访问局部变量str
        };
        
        char[] name={'N', 'B'};
        System.out.println(f.create(name)); //Hello World:NB
    }
}

实例变量和静态变量应用方法也类似。


四、方法引用

如果你的lambda表达式所做的只是用传递给lambda的参数调用另一个方法,则Java lambda实现提供了更简洁的方式表示该方法调用。

interface Learn {
    void study(String s);
}

public class Test {
    public static void main(String args[]) {
        Learn s = System.out::println; //注意是::,即只是想实现study()为System.out.println(s)
        s.study("Hello"); //Hello
    }
}

注意双冒号:: 它会向Java编译器发出信号,这是方法引用。引用的方法是双冒号之后的内容。拥有被引用方法的任何类或对象都在双冒号之前。


1. 静态方法引用

interface Finder {
    String find(String s1, String s2);
}

public class Test {
    public static String doFind(String s1, String s2) {
        return s1 + s2;
    }
    
    public static void main(String args[]) {
        Finder finder = Test::doFind;
        System.out.println(finder.find("Hello ", "World")); //Hello World
    }
}

由于 Finder.find() 和 Test.doFind() 方法的参数匹配,因此可以创建实现 Finder.find() 并引用 Test.doFind() 方法的 lambda 表达式。


2. 参数方法引用

也可以将其中一个参数的方法引用到lambda。注意简洁方式版本是如何引用单个方法的。Java编译器尝试将引用的方法与第一个参数类型相匹配,使用第二个参数类型作为被引用方法的参数。

interface Finder {
    int find(String s1, String s2);
}

public class Test {
    public static String doFind(String s1, String s2) {
        return s1 + s2;
    }

    public static void main(String args[]) {
        Finder finder = String::indexOf; //等价于 Finder finder = (s1, s2) -> s1.indexOf(s2);
        System.out.println(finder.find("Hello World", "World")); //6
    }
}


3. 实例方法引用

还可以从lambda表达式中引用实例方法。首先,让我们来看一个函数式接口定义:

interface Deserializer {
    int deserialize(String v1);
}

class StringConverter {
    public int convertToInt(String v1){
        return Integer.valueOf(v1);
    }
}

public class Test {
    public static void main(String args[]) {
        StringConverter stringConverter = new StringConverter();
        Deserializer des = stringConverter::convertToInt;
        System.out.println(des.deserialize("3")); //打印3
    }
}

Deserializer 接口表示一个组件,该组件能够将字符串"反序列化"为int。StringConverter 类的 convertToInt()方法与 Deserializer deserialize()方法的 deserialize()方法具有相同的签名。因此,我们可以创建 StringConverter 的实例,并从Java lambda表达式引用其 convertToInt()方法。


3. 构造方法引用

可以引用一个类的构造方法。你可以通过在类名后加上::new来完成此操作。在lambda表达式中引用构造方法:

interface Factory {
    String create(char[] val);
}

public class Test {
    public static void main(String args[]) {
        Factory factory = String::new; //等价于 Factory factory = chars -> new String(chars);
        char[] c = {'3','4','5'};
        System.out.println(factory.create(c)); //打印345
    }
}

此接口的 create()方法与String类中某个构造函数的签名匹配。因此,此构造函数可以被lambda表达式用到。


四、总结

1. Java中使用lambda表达式的前提条件是必须有一个函数式接口,也即是一个接口里面只有一个抽象方法。

2. 需要具有类型推导,参数个数类型需要匹配。

3. 当lambda表达式作为函数参数时,形参是一个对象类型,而不是lambda表达式的返回值。

 

 

 

参考:
Java Lambda表达式:https://www.cnblogs.com/three-fighter/p/13326627.html
一文看懂java中的Lambda表达式:https://zhuanlan.zhihu.com/p/112771403

 

posted on 2023-08-16 10:13  Hello-World3  阅读(96)  评论(0编辑  收藏  举报

导航