探索Java语言与JVM中的Lambda表达式

转载来源:http://www.admin10000.com/document/1291.html html

     Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法。(2013.01.02最后更新) java

  Lambda表达式,这个名字由该项目的专家组选定,描述了一种新的函数式编程结构,这个即将出如今Java SE 8中的新特性正被你们急切地等待着。有时你也会听到人们使用诸如闭包,函数直接量,匿名函数,及SAM(Single Abstract Method)这样的术语。其中一些术语彼此之间会有一些细微的不一样,但基本上它们都指代相同的功能。 git

  虽然一开始会以为Lambda表达式看起来很陌生,但很容易就能掌握它。并且为了编写可彻底利用现代多核CPU的应用程序,掌握Lambda表达式是相当重要的。 程序员

  须要牢记的一个关键概念就是,Lambda表达式是一个很小且能被看成数据进行传递的函数。须要掌握的第二个概念就是,理解集合对象是如何在内部进行遍历的,这种遍历不一样于当前已有的外部顺序化遍历。 算法

在本文中,咱们将向你展现Lambda表达式背后的动因,应用示例,固然,还有它的语法。 编程

为何你须要Lambda表达式 多线程

  程序员须要Lambda表达式的缘由主要有三个: 闭包

  1. 更紧凑的代码 并发

  2. 经过提供额外的功能对方法的功能进行修改的能力 oracle

  3. 更好地支持多核处理

  更紧凑的代码

  Lambda表达式以一种简洁的方式去实现仅有一个方法的Java类。

  例如,若是代码中有大量的匿名内部类–诸如用于UI应用中的监听器与处理器实现,以及用于并发应用中的Callable与Runnable实现–在使用了Lambda表达式以后,将使代码变得很是短,且更易于理解。

  修改方法的能力

  有时,方法不具有咱们想要的一些功能。例如,Collection接口中的contains()方法只有当传入的对象确实存在于该集合对象中时才会返回true。但咱们没法去干预该方法的功能,好比,若使用不一样的大小写方案也能够认为正在查找的字符串存在于这个集合对象中,咱们但愿此时contains()方法也能返回true。

  简单点儿说,咱们所指望作的就是”将咱们本身的新代码传入”已有的方法中,而后再调用这个传进去的代码。Lambda表达式提供了一种很好的途径来表明这种被传入已有方法且应该还会被回调的代码。

  更好地支持多核处理

  当今的CPU具有多个内核。这就意味着,多线程程序可以真正地被并行执行,这彻底不一样于在单核CPU中使用时间共享这种方式。经过在Java中支持函数式编程语法,Lambda表达式能帮助你编写简单的代码去高效地应用这些CPU内核。
例如,你可以并行地操控大集合对象,经过利用并行编程模式,如过滤、映射和化简(后面将会很快接触到这些模式),就可以使用到CPU中全部可用的硬件线程。

  Lambda表达式概览

  在前面提到的使用不一样大小写方案查找字符串的例子中,咱们想作的就是把方法toLowerCase()的表示法做为第二个参数传入到contains()方法中,为此须要作以下的工做:

  1. 找到一种途径,可将代码片段看成一个值(某种对象)进行处理

  2. 找到一种途径,将上述代码片段传递给一个变量

  换言之,咱们须要将一个程序逻辑包装到某个对象中,而且该对象能够被进行传递。为了说的更具体点儿,让咱们来看两个基本的Lambda表达式的例子,它们都是能够被现有的Java代码进行替换的。

  过滤

  你可能想传递的代码片段可能就是过滤器了,这是一个很好的示例。例如,假设你正在使用(Java SE 7预览版中的)java.io.FileFilter去肯定目录隶属于给定的路径,如清单1所示,

File dir = new File("/an/interesting/location/");
FileFilter directoryFilter = new FileFilter() {
        public boolean accept(File file) {
        return file.isDirectory();
    }
};
File[] directories = dir.listFiles(directoryFilter);
在使用Lambda表达式以后,代码会获得极大的简化,如清单2所示,
File dir = new File("/an/interesting/location/");
FileFilter directoryFilter = (File f) -> f.isDirectory();
File[] directories = dir.listFiles(directoryFilter);

赋值表达式的左边会推导出类型(FileFilter),右边则看起来像FileFilter接口中accept()方法的一个缩小版,该方法会接受一个File对象,在断定f.isDirectory()以后返回一个布尔值。

  实际上,因为Lambda表达式利用了类型推导,基于后面的工做原理,咱们还能够进一步简化上述代码。编译器知道FileFilter只有惟一的方法accept(),因此它一定是该方法的实现。咱们还知,accept()方法只须要一个File类型的参数。所以,f一定是File类型的。如清单3所示,

File dir = new File("/an/interesting/location/");
File[] directories = dir.listFiles(f -> f.isDirectory());

你能够看到,使用Lambda表达式会大幅下降模板代码的数量。

  一旦你习惯于使用Lambda表达式,它会使逻辑流程变得很是易于阅读。在达到这一目的的关键方法之一就是将过滤逻辑置于使用该逻辑的方法的侧边。

事件处理器

  UI程序是另外一个大量使用匿名内部类的领域。让咱们将一个点击监听器赋给一个按钮,如清单4所示,

Button button = new Button();
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    ui.showSomething();
    }
});
这多么代码无非是说”当点击该按钮时,调用该方法”。使用Lambda表达式就可写出如清单5所示的代码,
ActionListener listener = event -> {ui.showSomething();};
button.addActionListener(listener);
该监听器在必要时可被复用,但若是它仅需被使用一次,清单6中的代码则考虑了一种很好的方式。
button.addActionListener(event -> {ui.showSomething();});

在这个例子中,这种使用额外花括号的语法有些古怪,但这是必须的,由于actionPerformed()方法返回的是void。后面咱们会看到与此有关的更多内容。

  如今让咱们转而关注Lambda表达式在编写处理集合对象的新式代码中所扮演的角色,尤为是当针对两种编程风格,外部遍历与内部遍历,之间的转换的时候。

  外部遍历 vs. 内部遍历

  到目前为止,处理Java集合对象的标准方式是经过外部遍历。之因此称其为外部遍历,是由于要使用集合对象外部的控制流程去遍历集合所包含的元素。这种传统的处理集合的方式为多数Java程序员所熟知,尽管他们并不知道或不使用外部遍历这个术语。

  如清单7所示,Java语言为加强的for循环构造了一个外部迭代器,并使用这个迭代器去遍历集合对象,

List<String> myStrings = getMyStrings();
for (String myString : myStrings) {
    if (myString.contains(possible)){
        System.out.println(myString + " contains " + possible);
    }
}

使用这种方法,集合类表明着所有元素的一个”总体”视图,而且该集合对象还能支持对任意元素的随机访问,程序员可能会有这种需求。

  基于这种观点,可经过调用iterator()方法去遍历集合对象,该方法将返回集合元素类型的迭代器,该迭代器是针对同一集合对象的更具限制性的视图。它没有为随机访问暴露任何接口;相反,它纯粹是为了顺序地访问集合元素而设计的。这种顺序本性使得当你试图并发地访问集合对象时就会形成臭名昭著的ConcurrentModificationException。

  另外一种可选的方案就是要求集合对象要可以在内部管理迭代器(或循环),这种方案就是内部遍历,当使用Lambda表达式时会优先选择内部遍历。

  除了新的Lambda表达式语法之外,Lambda项目还包括一个通过大幅升级的集合框架类库。此次升级的目的是为了能更易于编写使用内部遍历的代码,以支持一系列众所周知的函数式编程典范。

  使用Lambda的函数式编程

  曾经,大多数开发者发现他们须要集合可以执行以下一种或几种操做:

  1. 建立一个新的集合对象,但要过滤掉不符合条件的元素。

  2. 对集合中的元素逐一进行转化,并使用转化后的集合。

  3. 建立集合中全部元素的某个属性的整体值,例如,合计值与平均值。这样的任务(分别称之为过滤,映射和化简)具备共通的要点:它们都须要处理集合中的每一个元素。

  程序不管是断定某个元素是否存在,或是判断元素是否符合某个条件(过滤),或是将元素转化成新元素并生成新集合(映射),或是计算整体值(化简),关键原理就是”程序必须处理到集合中的每一个元素”。
这就暗示咱们须要一种简单的途径去表示用于内部遍历的程序。幸运地是,Java SE 8为此类表示法提供了构建语句块。

  支持基本函数式编程的Java SE 8类

  Java SE 8中的一些类意在被用于实现前述的函数式典范,这些类包括Predicate,Mapper和Block–固然,还有其它的一些类–它们都在一个新的java.util.functions包中。

  看看Predicate类的更多细节,该类常被用于实现过滤算法;将它做用于一个集合,以返回一个包含有符合谓语条件元素的新集合。何为谓语,有不少种解释。Java SE 8认为谓语是一个依据其变量的值来断定真或假的方法。

  再考虑一下咱们以前看过的一个例子。给定一个字符串的集合,咱们想断定它是否包含有指定的字符串,但但愿字符串的比较是大小写不敏感的。

  在Java SE 7中,咱们将须要使用外部遍历,其代码将如清单8所示,

public void printMatchedStrings(List<String> myStrings) {
List<String> out = new ArrayList<>();
for (String s: myStrings) {
    if (s.equalsIgnoreCase(possible))
        out.add(s);
}
log(out);
}
而在即将发布的Java SE 8中,咱们使用Predicate以及Collections类中一个新的助手方法(过滤器)就可写出更为紧凑的程序,如清单9所示,
public void printMatchedStrings() {
Predicate<String> matched = s -> s.equalsIgnoreCase(possible);
log(myStrings.filter(matched));
}
事实上,若是使用更为通用的函数式编程风格,你只须要写一行代码,如清单10所示,
public void printMatchedStrings() {
log(myStrings.filter(s -> s.equalsIgnoreCase(possible)));
}

如你所见,代码依然很是的易读,而且咱们也体会到了使用内部遍历的好处。

  最后,让咱们讨论一下Lambda表达式语法的更多细节。

  Lambda表达式的语法规则

  Lambda表达式的基本格式是以一个可被接受的参数列表开头,以一些代码(称之为表达式体/body)结尾,并以箭头(->)将前二者分隔开。

  注意:Lambda表达式的语法仍可能会面临改变,但在撰写本文的时候,下面示例中所展现的语法是可以正常工做的。

  Lambda表达式很是倚重类型推导,与Java的其它语法相比,这显得极其不一样寻常。

  让咱们进一步考虑以前已经看过的一个示例(请见清单11)。若是看看ActionListener的定义,能够发现它只有一个方法(请见清单12)。

ActionListener listener = event -> {ui.showSomething();};
public interface ActionListener {
public void actionPerformed(ActionEvent event);
}

因此,在清单11右侧的Lambda表达式,可以很容易地理解为”这是针对仅声明单个方法的接口的方法定义”。注意,仍然必需要遵照Java静态类型的通常规则;这是使类型推导能正确工做的惟一途径。

  据此能够发现,使用Lambda表达式能够将先前所写的匿名内部类代码转换更紧凑的代码。

  还须要意识到有另外一个怪异的语法。让咱们再回顾下上述示例,如清单13所示,

FileFilter directoryFilter = (File f) -> f.isDirectory();
仅一瞥之,它看起来与ActionListener的示例类似,但让咱们看看FileFilter接口的定义(请见清单14)。accept()方法会返回一个布尔值,但并无一个显式的返回语句。相反,该返回值的类型是从Lambda表达式中推导出来的
public interface FileFilter {
    public boolean accept(File pathname);
}

这就能解释,当方法返回类型为void时,为何要进行特别处理了。对于这种情形,Lambda表达式会使用一对额外的小括号去包住代码部分(表达式体/body)。若没有这种怪异的语法,类型推导将没法正常工做–但你要明白,这一语法可能会被改变。

  Lambda表达式的表达式体能够包含多条语句,对于这种情形,表达式体须要被小括号包围住,但”被推导出的返回类型”这种语法将不启做用,那么返回类型关键字就必不可少。

  最后还须要提醒你的是:当前,IDE彷佛还不支持Lambda语法,因此当你第一次尝试Lambda表达式时,必需要格外注意javac编译器抛出的任何警告。

  结论

  Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性。应用得当,Lambda表达式可以使你写出简洁的代码,为已有方法增长额外的功能,并能更好地适应多核处理器。到目前为止,咱们能确定的是,你正急切地想去尝试Lambda表达式,因此咱也别啰嗦了…

  你能够从Lambda项目的主页中得到包含有Lambda表达式的Java SE 8快照版。一样地,在试用二进制包时,你也应该先阅读一下”Lambda项目状态”的相关文章,能够在此处找到它们。

相关文章
相关标签/搜索