【IT168 技术】作为一大饱受诟病的缺陷,Java作为一款编程语言由于太过啰嗦而受到开发者们的抱怨乃至指责,以至于我们不得不编写大量代码以保证每一项既定功能切实得以实现。而Lambda表达式的出现在一部分常见情况下解决了这个难题,同时也让Java在与竞争对手C#的多年缠斗当中占据了一定程度上的优势地位。
Lambda表达式伴随着Java SE 8的发布一同到来,就笔者个人而言它也算得上该版本中最值得关注的新增语言特性。Lambda表达式带来一种相对清晰且更为简洁的方法接口表达方式,同时也给Java Collection库带来了相当显著的改进,从而大大简化了对Collection的遍历、过滤以及数据提取流程。由Lambda表达式所带来的新型并发特性还提高了Java运行时在多核心环境下的性能表现。
▲细说Java 8 Lambda表达式
笔者曾经听到一些程序员表达的担忧之情,他们怀疑Lambda表达式会给Java的函数编程结构造成影响、进而损害其面向对象属性。事实上类似的说法早在六、七年之前就曾经在.Net开发领域闹得沸沸扬扬。历史经验告诉我们,尽管存在不同的声音、C#语言仍然借此实现了毋庸置疑的改进效果。
C#及VB.Net中的Lambda表达式与LINQ
Lambda表达式随Visual Studio 2008的发布而正式被纳入C#与VB.Net,其主要作用在于支持LINQ(即语言集成查询)。Lambda表达式是一种匿名函数,大家可以利用它创建委托或者表达树状范式。在C#当中,要想创建一条Lambda表达式,大家需要在Lambda运算符=>的左侧输入参数(如果有的话),并在另一侧输入表达式或者语句内容。举例来说,Lambda表达式x=>x * x的意思是指定一个名为x的参数,其返回值为x的平方。在VB.Net方面,我们则可以使用匿名Function或者Sub定义来创建Lambda表达式。
LINQ是一系列函数的统称,同样在Visual Studio 2008中与用户首次见面,它为C#以及Visual Basic语言语法带来了多项查询功能。LINQ在对SQL数据库、XML文档、ADO.Net数据集(可能由SQL数据库产生、也可能不是)以及.Net集合、文件与字符串等进行查询时功效卓著。最后要说的是LINQ to Object,这条术语是指利用LINQ对任意IEnumerable或者IEnumberable集合进行直接查询。Lambda在基于方法的LINQ查询当中被作为指向标准查询运算符方法——例如where——的参数。
LINQ与Lambda表达式已经在C#开发业界得到了广泛的认同与使用。我期待着Lambda表达式及其应用能够以同样的发展轨迹在Java开发业界占据一席之地。
Java中的匿名内部类
Java当中的匿名内部类可以算是通往Lambda表达式发展道路上的一种起步模式或者雏形。大家可以轻松对这些类进行联机定义且无需为其设定名称,举例来说:JButton testButton = new JButton("Test Button");
testButton.addActionListener(new ActionListener()
{@Override public void actionPerformed(ActionEvent ae){
System.out.println("Click Detected by Anon Class");
}
});
在以上示例中(来自甲骨文发布的官方教程),被添加到按钮中的ActionListener是由actionPerformed方法在匿名内部类中进行定义的,而并非利用经过命名的单独类。虽然这种处理方式能够略微降低代码量,但整体而言表达方式依然啰嗦。
只定义单独一项方法的接口过去被称为Single Abstract Method(即单独抽象方法)接口,如今在Java 8中则被更名为函数接口。如我们所见,函数接口与匿名内部类通常经由Lambda表达式加以使用。
Java中的Lambda语法
正如我们之前所说,C#中的Lambda运算符为=>。而在Java中,Lambda运算符则为-〉。(请大家不要抱怨二者之间的差异。如果语法完全保持一致,那么任何开发者都能够学会使用——这样开发水平的高低将无从体现。)
与C#中的Lambda表达式类似,Java 8 Lambda表达式当中包含一套参数列表。举例来说,(int x)-> x * x指定了一个名为x且返回值为x平方的整数参数。如大家所见,Java 8 Lambda表达式拥有类型化特性。幸运的是,当该类型能够通过上下文进行推断时,我们就可以将其省略。
请大家思考以下三种Lambda表达式:(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
现在看看之前提到过的ActionListener在利用Lambda表达式进行重新编写后是什么样子:JButton testButton = new JButton("Test Button");
testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listener"));
整个表达过程简洁多了,对吧?大家应该还会注意到,由于其中的“e”属于ActionListener的参数,因此它归于“对象类型”子句、而且其类型也能够正确推断得出。
标准函数接口
Java 8中的java.util.function包提供五种标准函数接口类型:
˙Predicate:作为参数进行传递的对象属性;
˙Consumer:在对象作为参数进行传递时,Consumer则作为执行操作;
˙Function: 将T转换成U;
˙Supplier:提供一个T实例(例如factory);
˙UnaryOperator:来自T-〉T的一元运算符;
˙BinaryOperator:来自(T,T)->T的二元运算符。
这些还仅仅是起步,因为大家总能对自己的接口加以定义,不过上面几种函数接口已经足以涵盖大部分大家平时常见的Lambda表达式使用情况。
Java中的Collection
Lambda表达式有助于简化Java Collection的使用流程,而且对于在Java 8之前就已经存在的Collection也同样适用。除此之外,Collection中还加入了大量能够与Lambda表达式顺畅协作的内容,其中包括在每个Iterator及Iterableinterface上被定义的新型forEach()方法。
举例来说(仍然引用自甲骨文官方教程),我们可以为Personclass定义一个List,并假设其下包含一个age成员:List pl = Person.createShortList();
接下来,我们可以定义一个Predicate来选择列表中的内容:Predicate allDrivers = p -> p.getAge() >= 16;
最后,我们能够对来自该List的选择内容进行操作:someClass.doSomething(pl, allDrivers);
经过比较,大家应该会发现Java 8在处理同一项任务时、代码要比前续版本表现得更为紧凑。
如果我们需要完成一些更为复杂的操作,使用前面提到过的forEach()方法相信能够达到目的:pl.forEach( p -> p.printWesternName() );
假设大家希望一次性使用多个Predicate,那么新近登场的filter()方法无疑是最好的帮手——它能够将Predicate作为参数加以处理,从而使用户得以把多个方法串联起来:pl.stream().filter(search.getCriteria("allPilots"))
.forEach(Person::printWesternName);
经过认真考量,大家应该会发现这种串联化filter在效率上要远远优于之前以手动方式为Collection成员编写循环。不符合前期标准的成员将全部被丢弃,而不再被继续传递到后续filter机制当中。
大家可能还注意到了我们在代码开头所使用的stream()方法——我们需要借此来启用串联机制。在这里,stream()方法将Collection作为输入内容,并将java.util.stream.Stream接口作为输出内容。所谓Stream,代表着一系列能够作为不同方法串联基础的元素。Stream能够以串行或者并行方式(使用parallelStream方法)付诸执行,这就给进一步提升性能表现带来了发挥空间。Stream会在使用结束后被自动处理掉。如果大家希望保留这些结果,可以将其复制到其它Collection当中。
我们当然还可以进一步讨论aggregate与map等方法,但相信说到这里大家已经对这套新机制拥有了初步概念。
综上所述,Java 8中的Lambda表达式给该语言带来了一系列改进。这种新型表达式的出现让一部分代码更易于查看及编写,特别是对于那些能够以函数形式表达的代码而言。Lambda表达式还为Java语言带来更加丰富的表达能力,并使多种操作在运行时当中获得更为高效的执行效果。通过LINQ与Lambda表达式在.Net开发环境下的表现,可以肯定的是其在为Java带来诸多改进之外并不会破坏现有面向对象特性。它的加入将使Java语言变得更加丰富、强大与精致。