Lambda表达式概述

  Lambda 表达式其实蕴含的是函数式编程中一个非常重要的思想:行为参数化。行为参数化就是一个方法接受不同的行为作为参数,以实现不同的功能。行为参数化可以使代码变得简洁,更重要的是它可以让代码适应不断变化的要求。
  在 Java 8 之前,实现行为参数化的最佳做法是采用策略模式,但 Lambda 表达式更加容易实现。

一、函数式接口

  函数式接口是只有一个抽象方法的接口。
  在 Java 8 中,允许接口拥有默认方法,也就是在没有实现类来实现这个方法时,接口也提供了一个默认实现。默认方法要用关键字 default 修饰。但是不管有多少个默认方法,只要接口只定义了一个抽象方法,那么这个接口就是函数式接口。
  Lambda 表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。Lambda 表达式本质上就是一个匿名函数。

二、Lambda 表达式的基本用法

Lambda 表达式的一般形式

  Lambda 表达式可写成这两种形式:(parameters) -> expression(parameters) -> {statements;}。此处 expression 和 statements 之间的区别值得一提:

  • expression 是指表达式,表达式就是包含变量和运算符(也可以没有运算符)的一个式子,比如说 aa+b 都是表达式。
  • statement 是指语句,语句就是由分号结尾的,比如 int i = 0;retrun "a"; 都是语句。表达式加上分号之后也可以成为语句。花括号内可以写多条语句。

如何使用 Lambda 表达式

  Java 8 之前,如果要实现一个函数式接口,比如很常见的 Runnable 接口,第一种做法是写一个实现类:

//Runnable接口的实现类
class RunnableImpl implements Runnable
{

    @Override
    public void run() {
        System.out.println("Hello,world!");
    }
    
}

public class NoLambdaTest {

    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread = new Thread(runnable);
        thread.start();
    }

}

  但是这种做法比较费事,所以更常见的做法是把 Runnable 接口的实现写成匿名内部类:

public class NoLambdaTest {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("Hello,world!");
            }
        });
        thread.start();
    }

}

  但不管是哪种写法,都仅仅是为了实现一个方法,为此而写出一个类是很不划算的;Lambda 表达式就很好地解决了这个问题:

public class LambdaTest {

    public static void main(String[] args) {

        //用Lambda表达式来实现接口
        Runnable runnable = () -> System.out.println("Hello,world!");
        
        Thread thread = new Thread(runnable);
        thread.start();
    }

}

三、Lambda 表达式的类型

基本概念

  前面说过 Labmda 表达式实际上是一个匿名函数,既然是函数就有参数类型和返回值类型。
  Lambda 表达式的参数类型与返回值类型要和接口中抽象方法所声明类型相同。比如 Runnable 中的 run 方法没有参数、没有返回值,那么对应的Lambda表达式也就没有参数、没有返回值;Predicate<T> 接口中的 test 方法接受一个 T 类型的对象,返回一个 boolean 值,那么 Lambda 表达式的参数就是一个 T 类型的对象,返回值是 boolean 值。
  通常使用 (paramters) -> returns 这样一个式子来描述 Lambda 表达式的类型,比如上面提到的 test 方法,实现它的 Lambda 表达式的类型就是 T -> boolean
  同一个 Lambda 表达式可以应用于不同的函数式接口,只要类型是对应的就行。


类型推断

  编译器会根据上下文推断出应该用什么函数式接口来配合 Lambda 表达式,所以可以无需显式地指出参数的类型。

Predicate<Integer> atLeast5 = x -> x > 5;

  编译器可以进行类型推断,因此无需指明 x 的类型。但要注意,推断的前提是已经指定这个泛型接口的具体类型 ,因此 Predicate<Integer> 这里的类型是不可省略的。


类型检查

  为了保证 Lambda 表达式和其所实现的函数式接口的类型是相同或者兼容的,编译器需要进行类型检查。下图展示了编译器对 Predicate<Integer> atLeast5 = x -> x > 5; 进行类型检查的过程。

Lambda 表达式类型检查

四、使用技巧

使用局部变量

  Lambda 表达式可以引用定义在 Lambda 表达式主体之外的局部变量,但是有一个限制:这个局部变量要么声明为 final,要么是事实上的 final ——仅赋值一次。

public class LambdaTest {

    public static void main(String[] args) {

        int sum = 3;
        //引用sum
        Runnable runnable = () -> System.out.println("1 + 2 = " + sum);
        
        Thread thread = new Thread(runnable);
        thread.start();
    }

}

  尽管 sum 没有用 final 修饰,但 sum 是一个事实上的 final 变量,因此引用是合法的。


避免装箱

  为了避免装箱操作以提高效率, Predicate<T> 等通用式函数式接口提供了针对原生数据类型的特化形式 IntPredicate,LongPredicate,DoublePredicate 等。


方法引用

  如果 Lambda 表达式的主体只是对一个已有方法的调用,那么就可以用方法引用来进一步简化 Lambda 表达式。方法引用的格式为 ClassName :: Method
  注意,方法引用所引用的方法的签名必须与上下文类型匹配。

构建方法引用

1.引用静态方法:比如引用 Integer.parseInt ,可以写为 Integer::parseInt
2.引用实例方法:比如引用 String 的 length 方法,可以写为 String::length
3.引用已有对象的实例方法:比如有一个局部变量 val ,要引用它的 toString 方法,可以写为 val::toString

引用构造器

  不管构造器的签名如何,通过方法引用来引用构造器的形式都是 ClassName::new 。参数个数不同的构造器适用于不同的函数式接口:

  • 无参构造器:适合 Supplier<T> 接口
  • 有一个参数的构造器:适合 Function<T,R> 接口
  • 有两个参数的构造器:适合 BiFunction<T,R> 接口

参考文献
《Java 8实战》Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft 著
《Java 8函数式编程》Richard Warburton 著

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容