JAVA 8:Lambdas表达式初体验

原文链接译文链接,译者:郑旭东

Lambdas项目是即将发布(译者注:原作者写本文的时候JAVA8尚未发布)的JAVA8中重要主题,同时它应该也是众多JAVA开发者最期待的功能。还有一个非常有意思的功能同Lambda表达式一起被加入到了JAVA中,它就是Defender方法。在这篇博文中,我想去探究一些更深层次的东西——JAVA如何在运行期表达Lambda表达式的和那些字节码指令在方法调度时被调用。

虽然JAVA8尚未发布,但你仍然可以通过“下载未正式发布版本”来体验JAVA8的魅力。

你想使用Lambdas?

如果你对其他包含Lambdas表达式的编程语言比较熟悉,如Groovy和Ruby,你可能会对它们并不像JAVA中那样简单而感到惊讶。在JAVA中,Lambda表达式是一个“SAM type”(译者注:SAM即Single Abstract Method),是一个只包含一个抽象方法的接口(是的,现在接口可以可以包含非抽象方法——Defener方法)。

举个例子,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表达式作为参数的方法将要怎么做?首先你必须先声明一个Functional接口的参数,然后你就可以传入一个Lambda表达式了。

当我们有了一个可以接受Functional接口作为参数的方法后,我们可以这样调用它:

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

实际上,相同的表达式可以被替换为一个方法引用,因为它只是一个使用相同参数的单一方法调用:

  execute(System.out::println);

然而,若方法的参数存在任何的转换操作,我们将不能使用方法引用,必须使用完整的Lambdas:

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

我认为这个语法是相当不错的。现在,我们在JAVA中有了相当优雅的Lambdas表达式解决方案,即使JAVA本身不具备functional类型。

JDK8中的Functional接口

我们明白,Lambda表达式在运行期被表示为一个functional接口(或者一个“SAM类型”)。并且虽然JDK已经包含了若干符合SAM标准的接口,如Runnable和Comparable,但这对于API的演进是明显不够的。因为在代码中肆意使用Runnable也是不可以接受的。

在JDK8中出现了一个新的包,java.util.function,它包含了一些可以在新的API中使用的functional接口。我们将不会在这里把它们都列出来,你可以稍后自己研究下这个包:)

似乎目前API演进得相当快,有些接口被加入了又被删除。比如,原来的JDK8提供了java.util.function.Block类,但当我写这篇文章时,最新的JDK8版本把这个类移除了。

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接口代替了并被所有collections库的新添加的方法所使用。举个例子,Collection接口定义的forEach方法如下:

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

比较令人感兴趣的一点是Consumer接口只定义了一个抽象方法——accept(T t),和一个defener方法——Consumer<T> chain(Consumer<? extend T> consumer)。这意味着有可能使用该接口进行链式调用。我不太清楚如何使用它,因为我没有在JDK中找到使用它的地方。

我也发现所有这些接口都带有@FunctionalInterface注解。除了在运行时的作用,这个注解还用于javac校验接口是否是真正的functional接口并且只定义了一个抽象方法。

若我们尝试编译这样的代码

@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工具开始阅读包含Lambda表达式的类的字节码对我来说是一件很自然的事情。

当前(JAVA 7或者以前),如果你想在JAVA中模拟Lambdas表达式,你不得不声明一个匿名的内部类。这将导致在编译后出现该类专有的class文件。并且如果你有多个这样的类,这些文件的文件名后将会有一个数字后缀。那么Lambda是怎么样的呢?

请看下下面的代码:

public class Main {

 @FunctionalInterface
 interface Action {
   void 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中必须有Lambda表达式的实现的表示。

$ 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

并且在常量池中可以找到bootstrap方法在运行期会和它相关联

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的静态方法后会如何。

 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

于此同时,若我删除了定义了Lambda表达式的代码后,这个代码就可以正常编译了。这实际上是告诉我们lamdba在其他结构编译之前就被捕获了,但这仅仅是我的假设。

请注意,在这个例子中Lambda表达式并没有捕获任何变量和引用类中的任何方法。这就是lambda&0方法为什么是静态的原因。如果它引用了类中任何一个变量或者方法,它将不会是一个静态的方法。因此请不要被这个例子误导。

总结

我们可以明确的说Lambda还有与其相关的一些功能将对JAVA产生深远的影响。它的语法十分棒并且一旦开发人员意识到这些功能将提高他们的生产力时,我们将会看到越来越多使用这些功能的代码。

我对Lambda编译后的样子十分感兴趣并且我也相当高兴我看到了invodkeynamic指令在完全没有匿名内部类 参与的情况下的应用。

文章转自 并发编程网-ifeve.com

时间: 2024-12-20 23:49:31

JAVA 8:Lambdas表达式初体验的相关文章

初学Java语言之多态初体验

首先我们有这样几个类,电脑,个人电脑,笔记本电脑. class Computer{ public void turnOn(){ } } 现在有了电脑这个类,那么个人PC,笔记本都是继承他. class PC extends Computer{ public void turnOn(){ System.out.println("PC has turn on"); } } class NB extends Computer{ public void turnOn(){ System.out

Java8初体验(一)lambda表达式语法

感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏差的地方,希望大家帮忙指出,我会持续修改和优化.本文是该系列的第一篇,主要介绍Java8对屌丝码农最有吸引力的一个特性-lambda表达式. java8的安装 工欲善其器必先利其器,首先安装JDK8.过程省略,大家应该都可以自己搞定.但是有一点这里强调一下(Windows系统):目前我们工作的版本

Java多态初体验

Java多态初体验本文面向Java初学者,我们在教材上经常会看到Java是多态的,可是一般的教科书只停留在理论上,很少有实际操作.现在把多态个概念用代码简单描述一下,由于本人水平有限,所以难免有不足之处.      首先我们有这样几个类,电脑,个人电脑,笔记本电脑. java 代码 class Computer{                  public void turnOn(){         }     }    现在有了电脑这个类,那么个人PC,笔记本都是继承他. java 代码

Kotlin 初体验:主要特征与应用

Kotlin 是一种针对 Java 平台的新编程语言.它简洁.安全.务实,并且专注于与 Java 代码的互操作性.它几乎可以用在现在Java 使用的任何地方 :服务器端开发.Android 应用,等等.本文我们将详细地探讨 Kotlin 的主要特征. 本文选自<Kotlin实战>. Kotlin初体验 让我们从一个小例子开始,来看看 Kotlin 代码长什么样子.这个例子定义了一个 Person 类来表示"人",创建一个"人"的集合,查找其中年纪最大的人

屌丝就爱尝鲜头——java8初体验

Java8已经推出,让我们看看他的魅力.让我们看看他改变较大的部分. 一.java8概述 Java8是由Oracle(甲骨文)公司与2014年3月27日正式推出的.Java8同时推出有3套语言系统,分别是Java SE8.Java SE Emebbled 8.Java ME8. Java SE8较以往的系统增强的功能有: ①增强了对集合式操作语言--lambda表达式的支持,"Lambda 表达式"(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演

Java8初体验(二)Stream语法详解

感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com 上篇文章Java8初体验(一)lambda表达式语法比较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例子中有很多Stream的例子.这些Stream的例子可能让你产生疑惑,本文将会详细讲解Stream的使用方法(不会涉及Stream的原理,因为这个系列的文章还是一个快速学习如何使用的). 1. Stream初体验 我们先来看看Java里面是怎么定义Stream的: A sequence of elem

Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream--&gt;干什么事(具体怎么做,就交给Stream)--》聚合

感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验(一)lambda表达式语法比较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例子中有很多Stream的例子.这些Stream的例子可能让你产生疑惑,本文将会详细讲解Stream的使用方法(不会涉及Stream的原理,因为这个系列的文章还是一个快速学习如何使用的). 1. Stream初体验 我们先来看看Java里面是怎么定义Stream的: A sequence of eleme

初体验JBossESB 及 部署使用 Hello World QuickStart

http://yulimin.javaeye.com/blog/52980 关键字:   ESB SOA     初体验JBossESB 及 部署使用 Hello World QuickStart1.下载 JBoss 4.0.5 GA 及 JBossESB 4.0 CR2 并解压,如果是src包的话,进入JBossESB目录运行ant install %JBoss_HOME% : C:/OpenSource/JBoss/4.0 %JBossESB_HOME% : C:/OpenSource/JB

zephir-(2)安装和初体验

zephir-安装和初体验 前言 先在这里感谢各位zephir开源技术提供者 zephir主要是解决了PHP开发人员尝试编写和编译PHP拓展所能执行的代码的语言.这是一个支持动态/静态类型的语言,熟悉PHP开发人员可以很好的进行开发.zephir这个名字的主要来源是(Zend Engine/PHP/Intermediate),今天首先介绍zephir的安装,接着会对zephir进行一些语法上面的介绍,希望大家喜欢! 注:笔者水平有限,说的不正确的地方希望大家多多指正,一同交流技术 附上: 喵了个