`

和Lambdas的第一次亲密接触(转载)

    博客分类:
  • java
 
阅读更多

java 8

  英文原文:Java 8: The First Taste of Lambdas

  Lambda 工程是即将到来的 Java8 的一大主题,可能也是程序员们最期待已久的东西。随着 Java lambdas 的到来,还有一个有趣的东西被附带的加进了 Java 语言——defender (守卫者)方法。在这篇文章里,我的目的是要看看面纱后的东西——看看在运行时环境里 lambdas 是表现的,在方法的调度过程中涉及到哪些字节码指令。

  尽管 Java 8 还没有正式发布,我们仍然可以下载各种平台上的早期预览版,在其上做简单的尝试。

  你也想试试 lambdas,是吗?

  如果你熟悉其它的还有 lambda 表达式的编程语言,比如 Groovy 或 Ruby,当第一眼看到 Java 里的 lambda 时,你也许会吃惊于它的不简单。在 Java 里,lambda 表达式是“SAM”(Single Abstract Method)——一个含有一个抽象方法的接口(是的,现在接口里可以含有一个非抽象的方法,defender 守卫方法)。

  举个例子,大家熟知的Runnable接口就可以完美的被当作一个 SAM 类型:

Runnable r = () -> System.out.println ("hello lambda!");

  这同样也适用于 Comparable 接口:

Comparator<Integer> cmp = (x, y) -> (x < y) ? -1 : ((x > y) ? 1 : 0);

  写成下面的样子也是一样的:

Comparator<Integer> cmp = (x, y) -> {
  return (x < y) ? -1 : ((x > y) ? 1 : 0);
};

  从中可以看出,单行的 lambda 表达式似乎是隐含了一个return语句。

  那么,如何写一个能接受 lambda 表达式作为参数的方法呢?这样,你需要先把这个参数声明成函数式的接口,然后把 lambda 传入:

interface Action {
   void run (String param);
}

public void execute (Action action){
   action.run ("Hello!");
}

  一旦有了一个能将函数式接口作为参数的方法,我们就可以像下面这样调用它:

execute ((String s) -> System.out.println (s));

  还可以更简洁,这个表达式可以被替换成对一个方法的引用,因为它只是单个方法,而且它们的参数是相同的:

execute (System.out::println);

  然而,如果参数上有任何其它形式的变化,我们就不能直接引用方法,必须写全 lambda 表达式:

execute ((String s) -> System.out.println ("*" + s + "*"));

  我觉得这种语法还是相当漂亮的,现在,Java 语言里有了一个非常优雅的 lambdas 解决方案,尽管 Java 里并不存在函数式类型。

  JDK 8 里的函数式接口

  我们已经知道,lambda 在运行时的表现形式是一个函数式的接口(或“SAM 类型”)——只有一个抽象方法的接口。尽管 JDK 里已经有了不少这样的接口,例如Runnable 和 Comparable ,它们符合这种标准,但很显然,对于一个新 API 的进化来说,这是不够的。我们不可能所有地方都用 Runnables 接口。

  在 JDK 8 里有个新包,java.util.function,里面包含了很多函数式接口,都是提供在新 API 里使用的。我不想把它们全列出来——你们自己可以去看一下,学习一下这个新包 

  但看起来这个新包在不断的变化,经常性的一些新接口会出现而另一些会消失。例如,以前曾有过 java.util.function.Block 这个类,最新的版本中却没有它,我写这篇博客时使用的版本是:

anton$ java -version
openjdk version "1.8.0-ea"
OpenJDK Runtime Environment (build 1.8.0-ea-b75)
OpenJDK 64-Bit Server VM (build 25.0-b15, mixed mode)

  我研究发现,它现在被 Consumer 接口替代,collection 包里的所有新方法都将使用它。例如,Collection接口里定义了forEach方法,如下:

public default void forEach (Consumer<? super T> consumer) {
  for (T t : this) {
    consumer.accept (t);
  }
}

  Consumer接口里一个有趣地方是,它实际上定义了一个抽象方法——accept (T t)和一个 defender 方法——Consumer<T> chain (Consumer<? extend T> consumer)。这就是说你可以链式调用这个接口。我不确定如何使用,因为我在 JDK 包里没有找到chain (..)的使用方法说明。

  我还发现所有的接口都使用了@FunctionalInterface 运行时注注解注释。这个注释不仅仅是个说明,它还被 javac 使用来验证这个接口是否真是一个函数式接口,是否至少有一个抽象方法在里面。

  所以,如果我们来编译下面的这段代码

@FunctionalInterface
interface Action {
  void run (String param);
  void stop (String param);
}

  编译器会告诉我们:

java: Unexpected @FunctionalInterface annotation
  Action is not a functional interface
    multiple non-overriding abstract methods found in interface Action

  而下面的就能编译通过:

@FunctionalInterface
interface Action {
  void run (String param);
  default void stop (String param){}
}

  反编译 lambdas

  我对语法语言特征其实并不是很好奇,我更好奇的是这些特征在运行时的表现形式,这就是为什么我像往常一样,拿起我喜爱的javap工具,开始查看 lambdas 里的这些类的字节码

  目前(在 Java 7 之前),如果你想在 Java 里模拟 lambdas,你需要定义一个匿名的内部类。它在编译后会产生一个具体的 class。如果你在一段代码里定义了多个这样的类,你会发现这些类后面会跟着一些数字。那 lambdas 也会这样吗?

  看看下面的这段代码:

public class Main {

  @FunctionalInterface
  interface Action {
    Object run (String s);
  }

  public void action (Action action){
    action.run ("Hello!");
  }

  public static void main (String[] args) {
    new Main () .action ((String s) -> System.out.print ("*" + s + "*"));
  }

}

  编译产生了两个类文件:Main.class 和 Main$Action.class,没有匿名类实现里那样的序号化的类。那么,在Main.class里应该会有一些东西来代表我在 main 方法里定义的 lambdas 表达式的实现。

$ javap -p Main 

Warning: Binary file Main contains com.zt.Main
Compiled from "Main.java" public class com.zt.Main {
  public com.zt.Main ();
  public void action (com.zt.Main$Action);
  public static void main (java.lang.String[]);
  private static java.lang.Object lambda$0(java.lang.String);
}

  啊哈!编译类了产生了lambda$0方法!使用-c -v指示符会让我们看到真正的字节码,以及常量池的定义。

  main 方法里显示,invokedynamic 被用来调度这个调用:

public static void main (java.lang.String[]);
  Code:
   0: new #4 // class com/zt/Main 3: dup          
   4: invokespecial #5 // Method "":()V 7: invokedynamic #6, 0 // InvokeDynamic #0:lambda:() Lcom/zt/Main$Action; 12: invokevirtual #7 // Method action:(Lcom/zt/Main$Action;)V 15: return

  而在常量池里,你也可以找到运行时的启动方法:

BootstrapMethods:
  0: #40 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;
  Method arguments:
    #41 invokeinterface com/zt/Main$Action.run:(Ljava/lang/String;) Ljava/lang/Object;
    #42 invokestatic com/zt/Main.lambda$0:(Ljava/lang/String;) Ljava/lang/Object;
    #43 (Ljava/lang/String;) Ljava/lang/Object;

  你会发现到处都是在使用 MethodHandle API,但我们现在不打算深入到里面。现在我们可以确认一点,我们的定义是引用了编译出来lambda$0方法。

  我很好奇,如果我定义一个相同名字的静态方法会怎样——毕竟“lambda$0”是一个有效的标识符!于是,我定义了自己的lambda$0方法:

public static Object lambda$0(String s){ return null; }

  而编译失败,编译器不允许我在代码了拥有这个方法:

java: the symbol lambda$0(java.lang.String) conflicts with a 
           compiler-synthesized symbol in com.zt.Main

  同时,如果我删掉这段定义 lambdas 表达式的代码,程序能顺利编译通过。这就是说,lambdas 表达式在编译期间会比类里的其它数据早先分析,不过这只是我的猜测。

  请注意:在这个例子中,lambda 并没有去引用任何变量,也没有引用类内部的任何方法。这就是为什么产生的lambda$0方法是静态的。如果 lambdas 引用了上下文中的变量或方法,那生成的将是一个非静态方法。所以,不要被这个例子误导——lambdas 是可以捕获上下文环境内容的!

  总结 lambdas

  我可以毫无疑问的说,lambdas 和伴随它一起的各种特征(守卫方法(defender)

  ,升级的集合类库)将很快给 Java 带来巨大的冲击。它的语法相当的简单,一旦程序员们意识到这些功能给开发效率带来的好处,我们将会看到大量的程序员都会运用这个功能。

  看看 lambdas 会编译成什么样子,这对于我来说是一件非常有趣的事情,我很开心,因为我看到这些所有的invokedynamic指令调用都没有出现匿名内部类。

分享到:
评论

相关推荐

    lambda演算和图灵机

    它包括一条变换规则(变量替换)和一条函数定义方式。 λ演算表达了两个计算机计算中最基本的概念“代入”和“置换”。“代入”我们一般理解为函数调用,或者是用实参代替函数中的形参;“置换”我们一般理解为变量...

    Lambda表达式.docx

    Lambda表达式通常用于简化那些只需要一次性使用的简单功能实现,比如作为参数传递给方法。在Java 8中,它们常用于集合操作(如`Stream API`)、事件处理、并行处理等。在上面的示例中,展示了如何用Lambda表达式...

    LAMBDA(MATLAB)

    MATLAB版本的LAMBDA算法为用户提供了一个高效且易于使用的工具,包含了源码和详细的说明手册。 **一、LAMBDA算法的核心概念** 1. **模糊度**:在GPS测量中,相位观测值是浮点数,包含一个整数部分(模糊度)和一个...

    2_Lambda表达式.zip

    例如,以下代码展示了如何使用Lambda表达式和`filter()`、`map()`和`reduce()`方法找出一个整数列表中的最大值: ```java List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5); OptionalInt max = numbers....

    lambda表达式和递归算法

    lambda 表达式是一种匿名函数,可以用来定义小的、单次使用的函数。它通常用于数据处理、事件处理和其他需要临时函数的情况。lambda 表达式的基本语法是:`lambda arguments : expression` 在 C# 中,lambda 表达式...

    java8lambda表达式Demo

    Java 8 是一个重要的Java平台版本,因为它引入了许多新特性,其中最显著的就是Lambda表达式。Lambda表达式是函数式编程的关键元素,它允许我们以更简洁、更易读的方式编写代码,特别是在处理集合和并发任务时。在这...

    Lambda教程

    【Lambda表达式】是C#编程语言中的一种强大特性,它允许程序员定义匿名函数,即没有名称的函数。Lambda表达式简洁、灵活,常用于事件处理、LINQ查询以及表达式树等场景。 1. **Lambda简介** Lambda表达式的基本...

    亚马逊 aws Lambda demo

    总之,这个"Maven demo"是一个实践性很强的学习资源,通过它,我们可以掌握如何在AWS Lambda上构建和部署Java应用程序,理解无服务器计算的优势,以及如何利用Lambda实现灵活、可扩展的云服务。无论你是初学者还是...

    Lambda表达式

    Lambda 表达式是一种匿名函数,可以包含表达式和语句,并且可用于创建委托或表达式树类型。所有 Lambda 表达式都使用 Lambda 运算符 =&gt;。该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。 ...

    C++ Lambda Story - From C++98 to C++20.pdf

    本书《C++ Lambda Story - From C++98 to C++20》详细讲述了Lambda表达式的演变历程,从早期的C++98到现代的C++20,展示了这一特性的逐步发展和完善。 Lambda表达式的核心概念在于它能够使函数对象的定义更加简洁和...

    Lambda表达式.zip

    Lambda表达式是Java编程语言中的一个关键特性,它在Java 8中被引入,极大地简化了函数式编程。Lambda表达式本质上是匿名函数,可以被用作方法参数或局部变量,使得代码更加简洁且易于理解。这篇教程是针对初学者准备...

    JavaLambda表达式和匿名类.pdf

    虽然在单次执行时,Lambda表达式和匿名类的差异可能并不显著,但在大量重复或并发执行的情况下,Lambda的性能优势会显现出来。这是因为Lambda表达式通常会产生更小的字节码,减少了内存分配和垃圾收集的压力。特别是...

    将字符串转换为lambda表达式

    `lambda`函数通常用于需要一次性、短小的函数,比如作为参数传递给高阶函数,如`map()`、`filter()`或`reduce()`。 将字符串转换为`lambda`表达式主要涉及两个步骤:解析字符串和构建函数对象。这个过程通常需要...

    C++ 11 lambda 表达式

    C++ 11引入了lambda表达式,这是一个强大的特性,极大地增强了C++的函数式编程能力。Lambda表达式允许在程序中直接定义匿名函数,并且可以直接在需要的地方使用,无需预先声明。这对于处理回调函数、简化算法实现...

    docker-lambda, Docker 映像和测试活动 AWS Lambda环境的测试运行者.zip

    docker-lambda, Docker 映像和测试活动 AWS Lambda环境的测试运行者 docker沙箱本地环境几乎同样地复制了 live AWS Lambda环境,包括安装的软件和库,文件结构和权限,上下文对象和行为,甚至用户和运行进程都是相同...

    C++ 中的 Lambda 表达式

    - **Capture子句**:Lambda表达式的第一个组成部分是capture子句。这个子句用来指定Lambda表达式可以访问哪些外围作用域中的变量。例如,下面的Lambda表达式捕获了一个名为`x`的外部变量,并通过值的方式捕获它: ...

    PS Lambda 1.0_lambda_GNSS_SDR_GNSS_computing_PSLambda_

    "PS Lambda 1.0_lambda_GNSS_SDR_GNSS_computing_PSLambda_" 这个标题揭示了我们讨论的主题是关于PS Lambda 1.0版本的一个软件或库,它专注于GNSS(全球导航卫星系统)信号处理和计算。GNSS SDR(软件定义无线电)是...

    lambda演算资料集合

    Lambda演算是计算机科学中的一个核心概念,它由数学家阿隆佐·邱奇在20世纪30年代提出,主要用于研究函数定义、函数应用和函数组合。Lambda演算是一种形式化系统,它允许我们用纯函数式的方式表达计算。在这个资料...

    前端开源库-lambda-tools

    此外,Lambda Tools作为一个开源项目,意味着它持续接受社区的贡献和改进,不断适应AWS Lambda平台的发展,提供了强大的社区支持和持续的更新保障。如果你正在使用或计划使用AWS Lambda构建前端微服务,那么Lambda ...

Global site tag (gtag.js) - Google Analytics