lambda 表达式的使用

一、什么是 lambda 表达式

Java8 是我们使用最广泛的稳定 Java 版本,lambda 就是其中最引人瞩目的新特性。lambda 是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,可以使代码看起来更加简洁。是不是听得一脸懵逼?我举个例子你就明白了。

package com.nianchen.lambda;

import org.junit.Test;

public class InitialExample {
    @Test
    public void innerClassMain() {

        //匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类写法");
            }
        }).start();
    }

    @Test
    public void lambdaMain() {

        //lambda表达式 写法
        new Thread(() -> System.out.println("lambda 写法")).start();
    }
}

我们应该知道,实现线程有两种方法,一是继承 Thread 类,二是实现 Runnable 接口。那这里采用的就是后者,后者是一个函数式接口。

1.1 函数式接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

从 Runnable 源码可以看到,它是一个函数式接口。这类接口的特点是:用 @FunctionalInterface注解修饰(主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错),有且只有一个抽象方法。在原生 JDK 中的这类接口就可以使用 lambda 表达式。

上面的概念提到,把函数当做参数来使用。上面的 lambda 例子中,Thread 类的参数就是一个 Runnable 接口,lambda 就是实现这个接口并把它当做参数使用。所以上面的 () -> System.out.println ("lambda 写法") 就是一个整个 lambda 表达式的参数(注意与后面的方法参数区分开,后面会讲)。细品加粗这句话,可以总结出,lambda 表达式就是创建某个类的函数式接口的实例对象。 如:

Runnable runnable = () -> System.out.println("lambda 写法");

二、为什么需要 lambda 表达式

明白了什么是  lambda 表达式,那为什么要使用它呢?注意到使用 lambda 创建线程的时候,我们并不关心接口名,方法名,参数名。我们只关注他的参数类型,参数个数,返回值。所以原因就是简化代码,提高可读性

三、如何使用 lambda 表达式

3.1 lambda 语法

// 格式遵循: (接口参数)->表达式(具体实现的方法)
(paramters) -> expression 或 (parameters) ->{ expressions; }

                                                                                       lambda 语法例子

具体解释,如上图。此外,lambda 语法注意点:

  • 可选类型声明:方法参数不需要声明参数类型,编译器可以统一识别参数值。

  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号

  • 可选的大括号:如果具体实现方法只有一个语句,就不需要使用大括号 {}

  • 可选的返回关键字:如果具体实现方法只有一个表达式,则编译器会自动返回值,如果有多个表达式,括号中就需要指定明表达式返回了一个数值。

3.2 使用示例:

package com.nianchen.lambda;

public class Example {

    // 定义函数式接口,只能有一个抽象接口,否则会报错
    // 希望在编译时检测报错,请加 @FunctionalInterface 注解
    @FunctionalInterface
    public interface Hello{
        String hi();
    }

    public interface Hello2{
        String hei(String hello);
    }

    public interface Hello3{
        String greet(String hello, String name);
    }

    public static void main(String[] args) {

        //无参数
        Hello no_param = () -> "hi, no param";
        Hello no_param2 = () -> {
            return "hi, no param2";
        };
        System.out.println(no_param.hi());
        System.out.println(no_param2.hi());

        System.out.println("================");

        //单个参数,一条语句,可以省略大括号和return
        Hello2 param = name -> name;
        Hello2 param2 = name -> {
            return name;
        };
        System.out.println(param.hei("hei,单个参数"));
        System.out.println(param2.hei("hei,单个参数2"));

        System.out.println("================");

        //多个参数
        Hello3 multiple = (String hello, String name) -> hello + ", " + name + "省略大括号与return";
        Hello3 multiple2 = (hello, name) -> hello + ", " + name + "省略参数类型定义";
        Hello3 multiple3 = (hello, name) -> {
            System.out.println("多参数内部");
            return hello + ", " +name;
        };

        System.out.println(multiple.greet("hello", "顺利的运行,"));
        System.out.println(multiple2.greet("hello", "顺利的运行,"));
        System.out.println(multiple3.greet("hello", "顺利的运行"));
    }
}

3.3 方法引用

看一个简单的方法引用例子:

Consumer<String> sc = System.out::println;
sc.accept("lambda 示例");

// 等效于
Consumer<String> sc2 = (x) -> System.out.println(x);
sc2.accept("lambda 实例");

Consumer 函数式接口源码:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

你可能有点懵,为什么可以这样写?别急我们分析一波:Consumer是一个函数式接口,抽象方法是 void accept (T t),参数是 T。那我们现在有这样一个需求,我想利用这个接口的抽象方法,做一下控制台打印。正常情况下,我们需要实现这个接口,实现它的抽象方法,来实现这个需求:

public class ConsumerImpl implements Consumer<String> {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
}

实现之后,这个抽象方法变具体了。作用就是控制台打印,那就意味着抽象方法刚好可以用实际方法:System.out.println (s) 来实现,所以我们可以使用方法引用。

总结:函数式接口的抽象方法实现恰好可以通过调用一个实际方法来实现时,就可以用方法引用。

方法引用的三种形式:

// 将抽象方法参数当做实际方法的参数使用
对象::实例方法 objectName::instanceMethod
// 将抽象方法参数当做实际方法的参数使用
类::静态方法 ClassName::staticMethod
// 将方法参数的第一个参数当做方法的调用者,其他的参数作为方法的参数
类::实例方法  ClassName::instanceMethod

我们来自定义一个方法类:

package com.nianchen.lambda;

public class Method {
    //静态方法
    public static void StaticMethod(String name){
        System.out.println(name);
    }

    //实例方法
    public void instanceMethod(String name){
        System.out.println(name);
    }

    //无参构造
    public Method(){}

    //有参构造
    public Method(String methodName){
        System.out.println(methodName);
    }
}

测试用例:

package com.nianchen.lambda;

import java.util.function.BiPredicate;
import java.util.function.Consumer;

public class MethodExample {
    public static void main(String[] args) {
        //静态方法引用 -- 通过类名调用
        Consumer<String> consumerStatic = Method::StaticMethod;
        consumerStatic.accept("静态方法");

        //等价于
        Consumer<String> consumerStatic2 = (x) -> Method.StaticMethod(x);
        consumerStatic2.accept("静态方法2");

        System.out.println("================");

        //非静态方法引用 -- 通过实例调用
        Method method = new Method();
        Consumer<String> consumerInstance = method::instanceMethod;
        consumerInstance.accept("对象的实例方法");

        //等价于
        Consumer<String> consumerInstance2 = (x) -> method.instanceMethod(x);
        consumerInstance2.accept("对象的实例方法2");

        System.out.println("================");

        // ClassName::instanceMethod 类的实例方法:把表达式的第一个参数当成 instanceMethod 的调用者,其他参数作为该方法的参数
        BiPredicate<String, String> sbp = String::equals;
        System.out.println("类的实例方法:" + sbp.test("a", "A"));

        //等效于
        BiPredicate<String, String> sbp2 = (x,y) -> x.equals(y);
        System.out.println("类的实例方法2:" + sbp2.test("a", "A"));


    }
}

输出结果:

静态方法
静态方法2
================
对象的实例方法
对象的实例方法2
================
类的实例方法:false
类的实例方法2:false

3.4 构造器引用

package com.nianchen.lambda;

import java.util.function.Function;
import java.util.function.Supplier;

public class ConstructMethodExample {
    public static void main(String[] args) {

        //构造方法方法引用 -- 无参数(可以使用方法引用)
        Supplier<Method> supplier = Method::new;
        System.out.println(supplier.get());

        //等价于
        Supplier<Method> supplier2 = () -> new Method();
        System.out.println(supplier2.get());

        //构造方法方法引用 -- 有参
        Function<String, Method> uf = name -> new Method(name);
        Method method = uf.apply("有参构造");
        System.out.println(method.toString());
        System.out.println(uf.apply("直接输出"));
    }
}

参考:https://www.cnblogs.com/kingsonfu/p/11047116.html

3.5 变量作用域

lambda 表达式只能引用 final 或隐性的具有final(不用声明为 final,但是必须不可被后面的代码修改)的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

package com.nianchen.lambda;

public class VariableScopeTest {
    //定义一个接口
    public interface Converter<T1,T2>{
        void convert(int i);
    }

    public static void main(String[] args) {

        //定义为final 强制不能修改
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        //输出结果为3
        s.convert(2);
    }
}

变量不声明为 final ,修改变量后,导致可以修改外部变量报错:

int num = 1;
num++;
//表达式中num会报错(Variable used in lambda expression should be final or effectively final)
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);

此外,在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量

String str= "";
// 同为 str 变量名,编译会出错
Comparator<String> comparator = (str, str1) -> Integer.compare(str.length(), str1.length());

四、十大 lambda 表达式示例

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值