美文网首页
Java lambda表达式

Java lambda表达式

作者: 0x70e8 | 来源:发表于2018-04-09 20:47 被阅读0次

    什么是lambda表达式

    看看lambda表达式在其他语言中怎么使用的:

    在python中有lambda表达式,在python中可以使用它定义匿名方法,并赋值给一个变量,然后那个变量就成为了一个方法,比如

    f = lambda x:  (x + 1 ) if x > 100 else x - 1
    # 相当于
    def f(x):
        return (x + 1 ) if x > 100 else x - 1;
    
    

    或者把lambda表达式传给一个方法,例如:

    def m(fun,x):
        fun(x)
    m(lambda x: print(x),1000)
    

    由上面的示例可以看出来,lambda表达式是一段可执行的代码块,可传入方法内执行,且可以赋给任意变量由变量执行。
    这其实也是支持函数式编程的特征,一段代码块可以在任何场所执行,不像java,需要借助对象这个承载来执行代码块。从python的执行方式就可以看出来,
    我们可以在一个py文件中输入print 'hello world',然后就可以直接执行输出;但是在java中,你需要先创建一个类,然后创建main方法,在main方法里面执行System.out.println("hello world");
    可以看出来,支持函数式编程能够减少很多的冗余代码,但是也因为灵活性使得代码的风格十分多变导致代码不好维护。
    Java引入lambda表达式的目的,自然是想引入函数式编程的优点,让代码简洁,这也是lambda表达式的作用。

    在java中怎么使用lambda表达式

    既然知道了什么是lambda表达式,那么再来了解一下再Java中使用这种语言特性,有哪些方式又有那些限制和注意事项。

    函数式接口

    在Java中,lambda是基于函数式接口的,也可以理解成它是替换函数式接口的实例的匿名对象,且它只能转换成函数式接口。
    那么什么是函数式接口呢?只有一个抽象方法的接口,只能有一个抽象接口,但是接口是可以包含default方法的(Java 8特性),如下的接口就是合法的:

    @FunctionalInterface
    interface FuncInter {
        void test();
    
        default void testdefault() {
            System.out.println("hello default");
        }
        // void test2(); error it is not a Functional interface
    }
    
    @FunctionalInterface
    interface FuncInter2 {
        void test(String x);
    }
    

    @FunctionalInterface是一个标识注解,表明本接口只有一个抽象方法,如果再次添加抽象方法,会在编译期报错,加上此注解可以防止误加抽象方法。

    函数式接口有了,怎么用呢,先看下在java8 以前,想要传入一段代码块是怎么做的。

        public static void main(String[] args) {
            // 匿名类的方法
            FuncInter2 f = new FuncInter2() {
    
                @Override
                public void test(String x) {
                    // TODO Auto-generated method stub
    
                }
    
            };
            f.test("hello");
        }
    
    

    如果不用匿名类,就需要创建一个类实现接口,再创建接口实例,最后调用test方法;

    试用lambda表达式

    既然说lambda表达式是用来替换函数式接口的,使用lambda的方法应该就可以减少相关代码的编写:

    @Test
        public void testLambdasyntax() {
    
            // lambda表达式是用来替换函数式接口的(匿名)实例
            /*
             * 无参抽象方法
             */
            // 1. 单行语句
            FuncInter fi = () -> System.out.println("hello");
            fi.test();
            // 2. 多行语句使用{}包围
            fi = () -> {
                // TODO
                System.out.println("line 1");
                System.out.println("line 2");
            };
            fi.test();
            /*
             * 有参抽象方法
             */
            FuncInter2 fi2 = (String a) -> System.out.println(a);
            fi2.test("www");
            // 可以推断参数类型可以不写类型
            fi2 = (a) -> System.out.println("no type " + a);
            fi2.test("hello ");
            // 单个参数,且参数类型可以推断,可以不加括号
            fi2 = a -> System.out.println("no parentheses " + a);
            fi2.test("hello ");
        }
    
        /**
         * 把lambda传给方法 </br>
         * 实际上就是把函数式接口的匿名对象传给方法 </br>
         * 可以理解成把函数传递给方法,但是在java里面实际上还是基于对象
         */
        @Test
        public void deliverToMethod() {
            // 定义一个局部类
            class MyClassNeedInterface {
                FuncInter fi = null;
    
                MyClassNeedInterface(FuncInter fi) {
                    this.fi = fi;
                }
    
                void doSomeThing() {
                    fi.test();
                }
    
                void doSomeThing(FuncInter2 fi, String x) {
                    fi.test(x);
                }
            }
            MyClassNeedInterface c = new MyClassNeedInterface(() -> System.out.println("This is from a lambda expression"));
            c.doSomeThing();
            String x = "a string ";
            // 等价于 c.doSomeThing(a->System.out.println(a), x);
            c.doSomeThing(System.out::println, x);
            c.doSomeThing(a -> System.out.println(a), x);
        }
    
    }
    
    

    确实简洁了很多。可以发现java中一个lambda表达式的形式是 参数、箭头、表达式,且可以根据情况省略括号或者参数类型,定义出来的表达式相当于一个接口的实例对象。
    箭头后面的表达式如果是多行代码,使用花括号括起来。
    实际上lambda表达式的用法,也就限于此类情况(替代函数式接口的匿名实例)。

    方法引用

    在示例deliverToMethod中有System.out::println这种形式的使用,是引用了已经存在的方法来填充接口的抽象方法,它支持三种形式:

    • instance::instanceMethod
    • Class::staticMethod
    • Class::instanceMethod 此种方法的调用 第一个参数是实例方法的调用者(方法的目标)

    构造器引用

    和上节的方法引用类似,只是引用的方法名是new。

    闭包

    闭包(closure)是计算机编程领域的专业名词,指可以包含自由(未绑定到特定对象)变量的代码块,子函数可以使用父函数中的局部变量。

    lambda表达式使得java拥有了闭包这个特性。 简单来说,闭包是有数据的行为(函数、方法),是可以读取其他函数内部变量的函数。
    lambda表达式具有代码块,那么自由变量是什么?所谓自由变量,是未绑定到特定对象的变量,那么外部的局部变量肯定是自由变量;
    子函数可以使用父函数中的局部变量,也就是代码块可以访问外围的局部变量,还要能够携带出去。

    @FunctionalInterface
    interface Closure {
        int test(int a);
    }
        @Test
        public void testClosure() {
            final int a = 100; // 这里的final可以不加但是得建立在后面不会修改的前提上,所以还是建议加上;
            Closure ci = (b) -> {
                System.out.println("I catch a int value of a :" + a);
                return (a + b);
            };
            // a month later
            int v = ci.test(1);
            System.out.println(v);
        }
    
    

    在java中,在lambda中访问的外部局部变量,必须是final变量,即引用不可修改,如果是基本类型,必须是常量。
    另外有一个很重要的规则,lambda表达式的体和使用这个表达式的块具有同一个作用域。所以同名变量会命名冲突
    如:

    @Test
        public void testSocpe() {
            int a = 1000;
            Closure closure = a -> a + 100; // ERROR
        }
        @Test
        public void testSocpe() {
            int b = 1000;
            Closure c = a -> this.add100(a);//this的作用域是在testScope方法里面
            System.out.println(c.test(b));// 1100
    
        }
    
        private int add100(int a) {
            return a + 100;
        }
    

    什么时候使用lambda表达式

    lambda表达式定义的可执行代码块,如果是需要立即执行的,可以直接写在执行方法里面,何必绕个圈子去调用接口的方法呢。所以lambda表达式的使用场景是,暂时不要执行的代码块,在未来因为某个事件触发而执行,所以重点在于延迟执行。

    jdk提供了一系列的函数式接口,涵盖很多使用场景。常用的有:

    接口 抽象方法 参数类型 返回值类型
    Runnable run - void
    Supplier<T> get - T
    Consumer<T> accept T void
    Predicate<T> test T boolean
    Java lambda表达式 Java lambda表达式
    • 参考文献
      [Java核心技术 卷I 第十版]

    相关文章

      网友评论

          本文标题:Java lambda表达式

          本文链接:https://www.haomeiwen.com/subject/ehlwhftx.html