Java8函数式编程-包教包会系列(三)

作者:曹伟,叩丁狼高级讲师。原创文章,转载请注明出处。

Lambda表达式语法揭秘

语法分析

我们先从之前写的简单的开启线程的例子开始分析:

传统写法:

@Test
public void testTradition() throws Exception {
    new Thread(new Runnable(){
        @Override
        public void run() {
            System.out.println("使用传统方式开启线程");
        }
    }).start();
}

使用Lambda表达式

@Test
public void testLambda() throws Exception {
    new Thread(()->System.out.println("使用Lambda方式开启线程")).start();;
}

在上述代码中,Threan类的构造器中需要传入一个Runnable接口的实现类,而Runnable接口中只有一个run方法(实现类中需要实现该方法并将任务代码写该方法中)

那既然我们知道Thread需要一个Runnable,并且只需要重写一个run方法,并且run方法中就是我们要写的任务代码,那我们为何不直接把我们要完成的任务代码传递给Thread呢?

所以我们就可以规定一种语法,将我们要完成的任务直接传递给Thread:

()->System.***out***.println("使用Lambda方式开启线程")

这种新的语法就是:“->”

image.png

“->”被称为Lambda 操作符或箭头操作符。

它将Lambda 分为两个部分:

左侧:指定了Lambda 表达式需要的所有参数,run方法不需要参数,所以可以直接写()

右侧:指定了Lambda 体,即Lambda 表达式要执行的任务、功能或者说是行为。

我们要完成的功能就是在run方法中输出一句话,所以直接写

System.***out***.println("使用Lambda方式开启线程")

可传递的匿名函数

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型。这个定义还是太模糊的,让我们从以下几个关键词慢慢道来。

●匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而做得多!

●函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型。

●传递——Lambda表达式可以作为参数传递给方法或存储在变量中。

●简洁——无需像匿名类那样写很多模板代码。

那到这里我们已经清楚了Lambda表达式的基本语法格式,那再继续思考一个问题:是不是所有的接口类型作为参数传递(传统写法就是匿名内部类)都可以使用Lambda表达式呢?

答案是不可以!因为只有接口中只有一个抽象方法的时候,Lambda表达式才可以正确的“猜测”到你传递的任务实际上是在实现接口中的唯一的那一个抽象方法!

那么我们把这种只有一个抽象方法的接口称作为“函数式接口”,只有函数式接口才可以使用Lambda表达式进行函数式编程!

函数式接口-@FunctionalInterface

只要确保接口中有且仅有一个抽象方法即可

格式:

修饰符 interface 接口名称 {

[public abstract] 返回值类型 方法名称(可选参数信息);

// 其他

}

为了便于区分并便于编译器进行语法校验,JDK8中还引入了一个新的注解:

@FunctionalInterface

该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

需要注意的是,即使不使用该注解,只要满足函数式接口的定义,该接口仍然是一个函数式接口,使用起来都一样。(可以类比下@Override 注解的作用)

我们可以去看下Runnable接口的源码,发现它就是一个函数式接口

image.png

那么我们也可以来定义一个简单的函数式接口:

@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();
}

那到这里我们已经知道了只要参数是函数式接口我们就可以使用Lambda表达式讲我们要完成的任务作为参数直接传递,但是接口中的方法是否有参数,有几个参数,是否有返回值情况可分为好多种呢!那我们就来具体看一下吧!

语法详解

以下是lambda表达式的语法特征:

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

2.可选的参数圆括号():一个参数无需定义圆括号,但多个参数需要定义圆括号()。

3.可选的大括号{}:如果主体包含了一个语句,就不需要使用{}。

4.可选的返回关键字return:如果主体只有一个表达式返回值则可以省略return和{}

public class LambdaSyntax {
    @Test
    public void test1() throws Exception {
        // 以下是lambda表达式的语法特征:
        // 1.可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
        Arrays.asList("a", "b", "c").forEach((String x) -> System.out.print(x));// 声明了参数类型
        Arrays.asList("a", "b", "c").forEach((x) -> System.out.print(x));// 省略了参数类型

        // 2.可选的参数圆括号():一个参数无需定义圆括号,但没有参数或多个参数需要定义圆括号()。
        Arrays.asList("a", "b", "c").forEach(x -> System.out.print(x));// 一个参数可以省略()
        new Thread(() -> System.out.println("没有参数")).start();// 没有参数不可以省略()
        Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 多个参数不可以省略()

        // 3.可选的大括号{}:如果主体包含了一个语句,就不需要使用{}。
        Arrays.asList("a", "b", "c").stream().sorted((x, y) -> {
            System.out.println("Lambda中有多条语句,return和{}不可以省略");
            return y.compareTo(x);
        }).forEach(x -> System.out.print(x));// 多条语句不可以省略{}
        Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 一条语句可以省略{}

        // 4.可选的返回关键字return:如果主体只有一个表达式返回值则可以省略return和{}
        Arrays.asList("a", "b", "c").stream().sorted((x, y) -> {
            System.out.println("Lambda中有多条语句,return和{}不可以省略");
            return y.compareTo(x);
        }).forEach(x -> System.out.print(x));// 多条语句不可以省略{}
        Arrays.asList("a", "b", "c").stream().sorted((x, y) -> y.compareTo(x)).forEach(x -> System.out.print(x));// 省略{}和return
    }
}

●总结

Lambda表达式极大的丰富了Java语言的表现力,简化了Java代码的编写,但是语法细节我们或许很难记住,那么只需要记住一条:可推断则可省略!

●注意

在某些情况下,类型信息可以帮助阅读者理解代码,这时需要手动声明类型,让代码便于阅读。有时省略类型信息更加简洁明了,还可以减少干扰,让阅读者关注业务逻辑,这时就可以省略;所以在开发中建议大家根据自己或项目组的习惯结合实际情况,进行类型声明或省略。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页