名扬数据:一起探索Java语言和JVM中的Lambda的表达式

Lambda表达式可使你写出简洁的代码,Lambda表达式是自JavaSE5引入泛型以来最重大的Java语言新特性。应用得当。为已有方法增加额外的功能,并能更好地适应多核处理器。本文是2012年度最后一期JavaMagazin中的一篇文章,Lambda表达式是自JavaSE5引入泛型以来最重大的Java语言新特性。介绍了Lamdba设计初衷,应用场景与基本语法。这个名字由该项目的专家组选定,Lambda表达式。描述了一种新的函数式编程结构,这个即将呈现在JavaSE8中的新特性正被大家急切地等待着。有时你也会听到人们使用诸如闭包,函数直接量,匿名函数,及SA MSinglAbstractMethod这样的术语。其中一些术语相互之间会有一些细微的不同,但基本上它都指代相同的功能。

如果代码中有大量的匿名内部类--诸如用于UI应用中的监听器与处置器实现,例如。以及用于并发应用中的Callabl与Runnabl实现--使用了Lambda表达式之后,将使代码变得非常短,且更易于理解。

方法不具备我想要的一些功能。例如,有时。Collect接口中的contain方法只有当传入的对象确实存在于该集合对象中时才会返回true但我无法去干预该方法的功能,比方,若使用不同的大小写方案也可以认为正在查找的字符串存在于这个集合对象中,希望此时contain方法也能返回true

所期望做的就是"将我自己的新代码传入"已有的方法中,简单点儿说。然后再调用这个传进去的代码。Lambda表达式提供了一种很好的途径来代表这种被传入已有方法且应该还会被回调的代码。多线程顺序能够真正地被并行执行,当今的CPU具备多个内核。这就意味着。这完全不同于在单核CPU中使用时间共享这种方式。通过在Java中支持函数式编程语法,Lambda表达式能帮助你编写简单的代码去高效地应用这些CPU内核。

能够并行地操控大集合对象,例如。通过利用并行编程模式,如过滤、映射和化简(后面将会很快接触到这些模式)就可使用到CPU中所有可用的硬件线程。想做的就是把方法toLowerCas表示法作为第二个参数传入到contain方法中,前面提到使用不同大小写方案查找字符串的例子中。为此需要做如下的工作:

可将代码片断当作一个值(某种对象)进行处置1.找到一种途径。

将上述代码片断传送给一个变量2.找到一种途径。

需要将一个顺序逻辑包装到某个对象中,换言之。并且该对象可以被进行传递。为了说的更具体点儿,让我来看两个基本的Lambda表达式的例子,都是可以被现有的Java代码进行替换的

过滤

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

清单1

Filedir=newFile"/an/interesting/location/"; 

FileFiltdirectoryFilt=newFileFilt{ 

   publicbooleanacceptFilefile{ 

       returnfile.isDirectori; 

   } 

}; 

File[]directori=dir.listFildirectoryFilt;

代码会得到极大的简化,使用Lambda表达式之后。如清单2所示,

清单2

Filedir=newFile"/an/interesting/location/"; 

FileFiltdirectoryFilt=Filef->f.isDirectori; 

File[]directori=dir.listFildirectoryFilt;

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

由于Lambda表达式利用了类型推导,实际上。基于后面的工作原理,还可以进一步简化上述代码。编译器知道FileFilt只有唯一的方法accept所以它肯定是该方法的实现。还知,accept方法只需要一个File类型的参数。因此,f肯定是File类型的如清单3所示,

清单3

Filedir=newFile"/an/interesting/location/"; 

File[]directori=dir.listFilf->f.isDirectori;

可以看到使用Lambda表达式会大幅降低模板代码的数量。

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

事件处置器

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

清单4

Buttonbutton=newButton; 

button.addA ctionListennewActionListen{ 

   publicvoidactionPerformActionEvente{ 

       ui.showSometh; 

   } 

};

调用该方法"使用Lambda表达式就可写出如清单5所示的代码,这多么代码无非是说"当点击该按钮时。

清单5

A ctionListenlisten=event->{ui.showSometh;}; 

button.addA ctionListenlisten;

但如果它仅需被使用一次,该监听器在必要时可被复用。清单6中的代码则考虑了一种很好的方式。

清单6

button.addA ctionListenevent->{ui.showSometh;};

这种使用额外花括号的语法有些古怪,这个例子中。但这是必需的因为actionPerform方法返回的void后面我会看到与此有关的更多内容。

尤其是当针对两种编程风格,现在让我转而关注Lambda表达式在编写处置集合对象的新式代码中所扮演的角色。外部遍历与内部遍历,之间的转换的时候。处置Java集合对象的规范方式是通过外部遍历。之所以称其为外部遍历,目前为止。因为要使用集合对象外部的控制流程去遍历集合所包含的元素。这种保守的处置集合的方式为多数Java顺序员所熟知,尽管他并不知道或不使用外部遍历这个术语。

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

清单7

List<String>myStr=getMyStr; 

forStringmyStr:myStr{ 

   ifmyString.containpossibl 

       System.out.printlnmyStr+"contain"+possibl; 

}

集合类代表着全部元素的一个"整体"视图,使用这种方法。并且该集合对象还能支持对任意元素的随机访问,顺序员可能会有这种需求。

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

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

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

但很容易就能掌握它而且为了编写可完全利用现代多核CPU应用顺序,虽然一开始会觉得Lambda表达式看起来很陌生。掌握Lambda表达式是至关重要的需要牢记的一个关键概念就是Lambda表达式是一个很小且能被当作数据进行传送的函数。需要掌握的第二个概念就是理解集合对象是如何在内部进行遍历的这种遍历不同于当前已有的外部顺序化遍历。将向你展示Lambda表达式背后的动因,本文中。应用示例,当然,还有它语法。为什么你需要Lambda表达式,顺序员需要Lambda表达式的原因主要有三个:

1.更紧凑的代码

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

3.更好地支持多核处理

顺序无论是判定某个元素是否存在或是判断元素是否符合某个条件(过滤)或是将元素转化成新元素并生成新集合(映射)或是计算总体值(化简)关键原理就是"顺序必需处置到集合中的每个元素"这就暗示我需要一种简单的途径去表示用于内部遍历的顺序。幸运地是JavaSE8为此类表示法提供了构建语句块。这些类包括PredicMapper和Block--当然,JavaSE8中的一些类意在被用于实现前述的函数式典范。还有其它一些类--都在一个新的java.util.funct包中。该类常被用于实现过滤算法;将它作用于一个集合,看看Predic类的更多细节。以返回一个包括有符合谓语条件元素的新集合。何为谓语,有很多种解释。JavaSE8认为谓语是一个依据其变量的值来判定真或假的方法。想判定它否包括有指定的字符串,再考虑一下我之前看过的一个例子。给定一个字符串的集合。但希望字符串的比拟是大小写不敏感的将需要使用外部遍历,JavaSE7中。其代码将如清单8所示,

清单8

publicvoidprintMatchedStrList<String>myStr{ 

   List<String>out=newArrayList<>; 

   forStrings:myStr{ 

       ifs.equalsIgnoreCaspossibl 

           out.adds; 

   } 

   logout; 

}

使用Predic以及Collect类中一个新的助手方法(过滤器)就可写出更为紧凑的顺序,而在即将发布的JavaSE8中。如清单9所示,

清单9

publicvoidprintMatchedStr{ 

   Predicate<String>match=s->s.equalsIgnoreCaspossibl; 

   logmyStrings.filtmatch; 

}

如果使用更为通用的函数式编程风格,事实上。只需要写一行代码,如清单10所示,

清单10

publicvoidprintMatchedStr{ 

   logmyStrings.filts->s.equalsIgnoreCaspossibl; 

}

代码依然非常的易读,如你所见。并且我也体会到使用内部遍历的好处。与Java其它语法相比,Lambda表达式非常倚重类型推导。这显得极其不同寻常。可以发现它只有一个方法(请见清单12让我进一步考虑之前已经看过的一个示例(请见清单11如果看看ActionListen定义。

清单11

A ctionListenlisten=event->{ui.showSometh;};

清单12

publicinterfacActionListen{ 

   publicvoidactionPerformActionEventevent; 

}

清单11右侧的Lambda表达式,所以。能够很容易地理解为"这是针对仅声明单个方法的接口的方法定义"注意,仍然必需要遵守Java静态类型的一般规则;这是使类型推导能正确工作的唯一途径。

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

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

清单13

FileFiltdirectoryFilt=Filef->f.isDirectori;

看起来与ActionListen示例相似,仅一瞥之。但让我看看FileFilt接口的定义(请见清单14accept方法会返回一个布尔值,但并没有一个显式的返回语句。相反,该返回值的类型是从Lambda表达式中推导出来的

清单14

publicinterfacFileFilt{ 

   publicbooleanacceptFilepathnam; 

}

当方法返回类型为void时,这就能解释。为什么要进行特别处置了对于这种情形,Lambda表达式会使用一对额外的小括号去包住代码部分(表达式体/bodi若没有这种怪异的语法,类型推导将无法正常工作--但你要明白,这一语法可能会被改变。对于这种情形,Lambda表达式的表达式体可以包括多条语句。表达式体需要被小括号解围住,但"被推导出的返回类型"这种语法将不启作用,那么返回类型关键字就必不可少。IDE似乎还不支持Lambda语法,最后还需要提醒你当前。所以当你第一次尝试Lambda表达式时,必需要格外注意javac编译器抛出的任何警告。