前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lambda 学习记录

Lambda 学习记录

作者头像
Li_XiaoJin
发布2022-06-10 21:39:28
4650
发布2022-06-10 21:39:28
举报
文章被收录于专栏:Lixj's BlogLixj's Blog

主要介绍如何构建Lambda,它的使用场合,以及如何利用它使代码更简洁。

Lambda——匿名函数

除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括Lambda(或匿名函数)。

❑ 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力。

❑ 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。

❑ 传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。

❑ Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。

如何构建Lambda,它的使用场合,以及如何利用它使代码更简洁。

Lambda管中窥豹

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

代码语言:javascript
复制
    // Java8之前的写法
        Comparator<Apple> weight = new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        };

    // Lambda
        Comparator<Apple> weight1 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

    // Lambda
        Comparator<Apple> weight2 = Comparator.comparing(Apple::getWeight);

❑ 参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。

❑ 箭头——箭头->把参数列表与Lambda主体分隔开。

❑ Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。

Java8中有效的Lambda表达式:

Lambda的基本语法是

代码语言:javascript
复制
    (parameters) -> expression

或(请注意语句的花括号)

代码语言:javascript
复制
    (parameters) -> {statements;}

根据上述语法规则,以下哪个不是有效的Lambda表达式? (1) ()-> {}(2) ()-> "Raoul"(3) ()-> {return "Mario"; }(4) (Integer i)-> return "Alan"+i;(5) (String s)-> {"IronMan"; } 答案:只有4和5是无效的Lambda。(1) 这个Lambda没有参数,并返回void。它类似于主体为空的方法:publicvoid run() {}。(2) 这个Lambda没有参数,并返回String作为表达式。(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:(Integer i)-> {return "Alan"+i; }。(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号和分号,如下所示:(String s)-> "Iron Man"。或者如果你喜欢,可以使用显式返回语句,如下所示:(String s)->{return "IronMan"; }。

Lambda示例:

在哪里以及如何使用Lambda

函数式接口

代码语言:javascript
复制
public interface Predicate<T> {
    boolean test(T t);
}

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

API中的一些其他函数式接口,如Comparator和Runnable。

用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。

代码语言:javascript
复制
public class Test {
    public static void main(String[] args) {

        // 使用Lambda
        Runnable r1 = () -> System.out.println("Hello World! 1");

        // 使用匿名类
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World! 2");
            }
        };


        process(r1);
        process(r2);
        process(() -> System.out.println("Hello World! 3"));

    }

    public static void process(Runnable r) {
        r.run();
    }
}
代码语言:javascript
复制
Hello World! 1
Hello World! 2
Hello World! 3

函数描述符

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。

Java 8中的常用函数式接口:

Lambda表达式是怎么做类型检查的。这个会在3.5节中详细介绍,编译器是如何检查Lambda在给定上下文中是否有效的。现在,只要知道Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法就好了,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样。

“为什么只有在需要函数式接口的时候才可以传递Lambda呢? ”语言的设计者也考虑过其他办法,例如给Java添加函数类型。 但是他们选择了现在这种方式,因为这种方式自然且能避免语言变得更复杂。 此外,大多数Java程序员都已经熟悉了具有一个抽象方法的接口的理念(例如事件处理)。

以下哪些是使用Lambda表达式的有效方式?

答案:只有1和2是有效的。 第一个例子有效,是因为Lambda()-> {}具有签名()-> void,这和Runnable中的抽象方法run的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的! 第二个例子也是有效的。事实上,fetch方法的返回类型是Callable。Callable基本上就定义了一个方法,签名是()-> String,其中T被String代替了。因为Lambda()-> "Trickyexample; -)"的签名是()-> String,所以在这个上下文中可以使用Lambda。 第三个例子无效,因为Lambda表达式(Apple a)-> a.getWeight()的签名是(Apple)->Integer,这和Predicate:(Apple)-> boolean中定义的test方法的签名不同。

关于 @FunctionalInterface

如果去看看新的Java API,会发现函数式接口带有@FunctionalInterface的标注这个标注用于表示该接口会设计成一个函数式接口。

如果你用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误消息可能是“Multiple non-overriding abstract methods foundin interface Foo”,表明存在多个抽象方法。请注意,@FunctionalInter-face不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override标注表示方法被重写了。

可以把它看作是一个标识。

把Lambda付诸实践:环绕执行模式

资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式。

例如,在以下代码中,高亮显示的就是从一个文件中读取一行所需的模板代码(注意你使用了Java 7中的带资源的try语句,它已经简化了代码,因为你不需要显式地关闭资源了):

代码语言:javascript
复制
    public static String processFiles() {
        try {
            BufferedReader br = new BufferedReader(new FileReader("data.txt"));
            return br.readLine();
        }
    }

最终效果:

Lambda仅可用于上下文是函数式接口的情况。

  1. 创建一个接口,一个能匹配BufferedReader-> String,还可以抛出IOException异常的接口。 @FunctionalInterface public interface BufferReaderProcessor { String process(BufferedReader br) throws IOException; }
  2. 把这个接口作为新的processFile方法的参数。 public static String processFiles(BufferReaderProcessor p) { try { BufferedReader br = new BufferedReader(new FileReader("data.txt")); return p.process(br); } }

任何BufferedReader-> String形式的Lambda都可以作为参数来传递,因为它们符合BufferedReaderProcessor接口中定义的process方法的签名。

现在只需要一种方法在processFile主体内执行Lambda所代表的代码。

请记住,Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。因此,可以在processFile主体内,对得到的BufferedReaderProcessor对象调用process方法执行处理:

现在可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件了。

代码语言:javascript
复制
// 处理一行
String res1 = processFiles((BufferedReader br1) -> br1.readLine());

// 处理两行
String res2 = processFiles((br2) -> br2.readLine() + br2.readLine());

使用函数式接口

Lambda和方法引用实战

  1. 使用匿名类
  1. 使用Lambda表达式

可以继续重写为:

Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。 可以写为:

使用方法引用

总结

  • Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
  • Lambda表达式让你可以简洁地传递代码。
  • 函数式接口就是仅仅声明了一个抽象方法的接口。
  • 只有在接受函数式接口的地方才可以使用Lambda表达式。
  • Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
  • Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate、Function<T, R>、Supplier、Consumer和BinaryOperator。
  • 为了避免装箱操作,对Predicate和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
  • 环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配和清理)可以配合Lambda提高灵活性和可重用性。
  • Lambda表达式所需要代表的类型称为目标类型。
  • 方法引用让你重复使用现有的方法实现并直接传递它们。
  • Comparator、Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可 Links: https://cloud.tencent.com/developer/article/2020533

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Lambda管中窥豹
  • 在哪里以及如何使用Lambda
    • 函数式接口
      • 函数描述符
      • 把Lambda付诸实践:环绕执行模式
      • 使用函数式接口
      • Lambda和方法引用实战
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档