资讯专栏INFORMATION COLUMN

Lambda 表达式

Null / 1222人阅读

摘要:例如上一章的苹果谓词接口只有一个抽象方法的接口能让把整个表达式作为函数式接口的实例。这个函数式接口是用来接收一个对象并对其进行处理。

Lambda 管中窥豹

可以把 Lambda 表达式理解为简洁地可传递的匿名函数的一种方式:没有名称,有参数列表、函数主体、返回类型和可能的异常。理论上讲,匿名函数做不到的事,Lambda 也做不了。后者只是让前者可读性更强,写得更轻松。

回顾上一章最后的那个 Lambda 表达式

(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

我们可以发现 Lambda 可以划分为三个部分:

参数 (Apple apple1)

箭头 ->

主体 "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

Lambda 的基本语法是这样的:

(parameters) -> expression

(parameters) -> { statements; }

在哪里以及如何使用 Lambda

我们可以在函数式接口中使用 Lambda。

函数式接口就是只定义一个抽象方法的接口。

例如上一章的苹果谓词接口

public interface ApplePredicate {
    boolean test(Apple apple);
}

只有一个抽象方法的接口能让 Lambda 把整个表达式作为函数式接口的实例。匿名内部类一样可以完成同样的事,只不过很笨拙。

JDK 中的 Runnable 接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

两种实现方式

        Runnable runnable1 = () -> System.out.println("Hello Word!");
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Word!");
            }
        };

这两钟实现结果是一样的。函数式接口的返回类型基本上就是 Lambda 的返回类型。

函数式接口一般都可以被 @FunctionalInterface 注解,这个注解就如同它的名字一样代表这个接口是函数式接口。并且它和 @Override 一样只是让编译期判断是否正确,运行期无关,并不是必需的。如果在一个接口中定义了多个抽象方法,并加上这个注解再编译的话,编译器便会给你的报错,因为这样的接口已经不符合函数式接口的定义了。

使用函数式接口

JDK 本身也自带了几个函数式接口,比如 Predicate、Consumer、Function。我们可以使用一下练练手。

Predicate

这个和上一章最后我们自己写的那个函数式接口,两者完全一样,都是用来做条件测试的谓词接口。

通过谓词过滤泛型集合

    public static  List filter(List list, Predicate predicate) {
        List result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t)) {
                result.add(t);
            }
        }
        return result;
    }

过滤掉字符串集合中空字符串

        List stringList = new ArrayList<>();
        stringList.add("hello");
        stringList.add("");
        stringList.add("lambda");

        // Lambda 实现
        Predicate stringPredicate = (String s) -> !s.isEmpty();
        // 匿名实现
        Predicate stringPredicate1 = new Predicate() {
            @Override
            public boolean test(String s) {
                return !s.isEmpty();
            }
        };
        List result = filter(stringList, stringPredicate);

这样空字符串就会被过滤掉,只剩下 hellolambda

Consumer

这个函数式接口是用来接收一个对象并对其进行处理。

遍历一个泛型集合

    public static  void forEach(List list, Consumer consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }

取出字符串集合里面的对象并打印输出

        // Lambda 实现
        Consumer consumer = (String s) -> System.out.println(s);
        // 匿名实现
        Consumer consumer1 = new Consumer() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        forEach(stringList, consumer);

这样就会打印输出 hello、 、lambda

Function

这个函数式接口是用来接收一个对象并映射到另一个对象。

接收一个集合对象并返回另一个集合对象

    public static  List map(List list, Function function) {
        List result = new ArrayList<>();
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }

接收一个字符串集合并映射成其长度的整型集合后返回

        // Lambda 实现
        Function function = (String s) -> s.length();
        // 匿名实现
        Function function1 = new Function() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        };
        List integerList = map(stringList, function);

这样 hello、 、lambda 就分别对应其长度 506

方法引用

方法引用可以看作对特定 Lambda 的一种快捷写法,本质上依然是 Lambda。它的基本思想是,如果一个 Lambda 代表的仅仅是 直接调用 这个方法而不是 描述如何去调用 这个方法,那最好还是用名称来调用它。

例如在上一章的苹果实例中我们需要用谓词判断是否成熟

        // 普通 Lambda 写法
        Predicate applePredicate1 = (Apple apple) -> apple.isAging();
        // 方法引用写法
        Predicate applePredicate2 = Apple::isAging;

我们可以把方法引用看作对 仅仅涉及单一方法 的 Lambda 的语法糖。

构造函数引用

对于一个现有的构造函数,我们可以利用它的名称和 new 来创建它的一个引用:ClassName::new

        // 普通 Lambda 创建对象
        // Supplier appleSupplier = () -> new Apple();
        // 构造函数引用创建无参对象
        Supplier appleSupplier = Apple::new;
        // 获取实例
        Apple apple1 = appleSupplier.get();
        
        // 构造函数引用创建有一个参数对象
        Function appleFunction = Apple::new;
        // 获取实例
        Apple apple2 = appleFunction.apply("red");

        // 构造函数引用创建有两个参数对象
        BiFunction appleBiFunction = Apple::new;
        // 获取实例
        Apple apple3 = appleBiFunction.apply("red", 1);

那么当参数有很多的时候怎么办呢?我们可以自定义一个函数式接口进行处理。

三个参数的构造函数引用接口

public interface TriFunction {
    R apply(T t, U u, V v);
}

调用也是类似的

        // 构造函数引用创建有三个参数对象
        TriFunction appleTriFunction = Apple::new;
        // 获取实例
        Apple apple4 = appleTriFunction.apply("red", 1, true);
Lambda 和方法引用实战 传递代码

我们如果要对一个苹果集合按照重量从小到大排序,首先肯定要进行判断大小,然后对其进行排序。按照我们已经经过一章多的学习,应该能很轻松地构建一个解决方案。

1、创建比较器

public class AppleComparator implements Comparator {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
}

2、调用 List 已经实现好的排序方法

public class Main {
    public static void main(String[] args) {
        List appleList = new ArrayList<>();

        // 重量为2的成熟红苹果 No.1
        Apple apple = new Apple();
        apple.setColor("red");
        apple.setWeight(2);
        apple.setAging(true);
        appleList.add(apple);

        // 重量为1的未成熟绿苹果 No.2
        apple = new Apple();
        apple.setColor("green");
        apple.setWeight(1);
        apple.setAging(false);
        // 现在 appleList 的顺序是放入的顺序 No.1、No.2
        appleList.add(apple);

        // 依照重量排序后 appleList 的顺序会变成 No.2、No.1
        appleList.sort(new AppleComparator());
    }
}

这只是简单的一个通过传递比较器来进行排序。下面我们会用匿名类来实现上一章学习的 应对不断变化的需求

使用匿名类

到这一步其实已经算得上符合正常软件工程设计了,可以舍去 AppleComparator 这样的实现方式。

        appleList.sort(new Comparator() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        });
使用 Lambda 表达式

紧接着我们可以更加高效地用 Lambda 实现。

        appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));

我们还可以进一步简化代码。Java 编译器会从目标类型自动推断出适合 Lambda 的返回类型。因此可以省略对传入参数的类型定义。

        // appleList 的类型定义是 List,传递进 sort() 的 Comparator 会自动定义泛型为 Apple,所以 Lambda 也可以自动推断为 Comparator 的 compare() 传入的类型。
        appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
使用方法引用

使用方法引用还可以更加让人通俗易懂。

Comparator 有个静态方法 comparing 可以接收 Function 函数式接口,用作比较依据。

        // 传入的 Function 本质是将苹果(Apple)映射成了苹果的重量(Integer)
        // Function function = (Apple o1) -> o1.getWeight();
        // 方法引用后
        Function function = Apple::getWeight;
        // 传入到 comparing 静态方法
        Comparator.comparing(function);

所以最后可以简化到极致

        // 代表按照苹果的重量进行比较后排序
        appleList.sort(Comparator.comparing(Apple::getWeight));
复合 Lambda 表达式的有用方法

Java 8提供了允许进行复合的方法。比如我们可以让两个谓词之间做 or 操作,组成一个更加强大的谓词。

比较器复合

如果想要从大到小排序苹果的重量(默认的 sort() 是从小到大排序)

        appleList.sort(Comparator.comparing(Apple::getWeight).reversed());

但是如果两个的苹果重量一样,我们需要再根据颜色或者是否成熟来排序呢?

我们可以使用 thenComparing 方法

        appleList.sort(Comparator
                // 从大到小排列苹果的重量
                .comparing(Apple::getWeight).reversed()
                // 然后按照颜色字母顺序
                .thenComparing(Apple::getColor)
                // 然后按照是否成熟
                .thenComparing(Apple::getAging));

这样就可以创建一个比较器链了。

谓词复合

谓词接口包括三个方法:negate、and 和 or,我们可以以此创建复杂的谓词。

选出苹果不是红的

        Predicate isRed = (Apple o1) -> "red".equalsIgnoreCase(o1.getColor());
        Predicate noRed = isRed.negate();

选出苹果不是红的且成熟的

        Predicate noRedAndIsAging = noRed.and(Apple::getAging);

选出苹果不是红的且成熟的或重量大于1

        Predicate noRedAndIsAgingOrHeavey = noRedAndIsAging.or((Apple o1) -> o1.getWeight() > 1);

总结起来,除了 isRed 谓词要在第一步写,其余的地方都可以一句话写完

        Predicate predicate = noRed
                .and(Apple::getAging)
                .or((Apple o1) -> o1.getWeight() > 1);

这样从简单的 Lambda 出发,可以构建更加复杂的表达式,但读起来会更加轻松。注意,and 和 or 的是按照链中的位置执行。

函数复合

Function 接口包括两个方法:andThen 和 compose,它们都会返回一个 Function 的实例。

我们可以用 Function 定义三个函数 f(x)、g(x)和 g(x),先看看 andThen()

        // f(x) = x + 1
        Function f = x -> x + 1;
        // g(x) = x * 2
        Function g = x -> x * 2;
        // h(x) = f(g(x))
        Function h = f.andThen(g);

传入 x 进行运算

        // 结果为4
        int result = h.apply(1);

compose()

        // h(x) = g(f(x))
        h = f.compose(g);
        // 结果为3
        result = h.apply(1);

第三章的东西有点多,需要反复消化理解。

Java 8 实战 第三章 Lambda 表达式 读书笔记

欢迎加入咖啡馆的春天(338147322)。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70186.html

相关文章

  • JAVA Lambda达式

    摘要:语言是强类型面向对象的语言,所以必须提供一种数据类型作为表达式的返回值类型符合中函数格式的定义符合面向对象规则,所以最终表达式要有一个映射成对象的过程。定一个函数式接口我们在接口里定义了一个没有参数返回值的抽象方法。 在JAVA中,Lambda 表达式(Lambda expression)是一个抽象方法的实现。这个抽象方法必须是在接口中声明的,而且实现类只需要实现这一个抽象方法,我们称...

    Cheng_Gang 评论0 收藏0
  • Java Lambda达式

    摘要:表达式的主要作用就是代替匿名内部类的烦琐语法。从这点来看,表达式的代码块与匿名内部类的方法体是相同的。与匿名内部类相似的是,由于表达式访问了局部变量,该局部变量相当于与一个隐式的修饰,因此不允许对局部变量重新赋值。 函数式接口 函数式接口(Functional Interface)就是一个只有一个抽象方法(可以包含多个默认方法或多个static方法)的普通接口,可以被隐式转换为lamb...

    lewif 评论0 收藏0
  • 深入浅出 Java 8 Lambda 达式

    摘要:在支持一类函数的语言中,表达式的类型将是函数。匿名函数的返回类型与该主体表达式一致如果表达式的主体包含一条以上语句,则表达式必须包含在花括号中形成代码块。注意,使用表达式的方法不止一种。 摘要:此篇文章主要介绍 Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等。本文系 OneAPM 工程师编译整理。 Java 是一流的面向对象语言,除了部分简...

    wdzgege 评论0 收藏0
  • Java 8 Lambda 达式详解

    摘要:表达式简介表达式是一个匿名函数对于而言并不很准确,但这里我们不纠结这个问题。如果表达式的正文有一条以上的语句必须包含在大括号代码块中,且表达式的返回值类型要与匿名函数的返回类型相同。 版权声明:本文由吴仙杰创作整理,转载请注明出处:https://segmentfault.com/a/1190000009186509 1. 引言 在 Java 8 以前,若我们想要把某些功能传递给某些方...

    haoguo 评论0 收藏0
  • 转 | Java8初体验(一)lambda达式语法

    摘要:初体验下面进入本文的正题表达式。接下来展示表达式和其好基友的配合。吐槽一下方法引用表面上看起来方法引用和构造器引用进一步简化了表达式的书写,但是个人觉得这方面没有的下划线语法更加通用。 感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.com 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘。因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏...

    Lucky_Boy 评论0 收藏0
  • 函数式编程与面向对象编程[1]: Lambda达式 函数柯里化 高阶函数

    摘要:函数式编程与面向对象编程表达式函数柯里化高阶函数之剑什么是表达式例子定义表达式是一个匿名函数,表达式基于数学中的演算得名,直接对应于其中的抽象,是一个匿名函数,即没有函数名的函数。 函数式编程与面向对象编程[1]: Lambda表达式 函数柯里化 高阶函数.md 之剑 2016.5.2 11:19:09 什么是lambda表达式 例子 For example, in Lisp the...

    张金宝 评论0 收藏0

发表评论

0条评论

Null

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<