简介
在使用java8之前,我们在处理一些包含有单个方法的接口时,一般是通过实现具体类或者匿名类的方式来处理的。这种方式能实现所期望的功能,而且也是传统的一切皆对象思想的体现。从实现的细节来看,却显得比较繁琐。在引入了lambda表达式这种新特性之后,我们有了一种更加简练的方式来实现对应的功能特性,当然,也带来了一种函数式编程思想上的转变。
简单示例
我们先来看一个简单的示例,假定我们首先定义如下的类:
public class Apple { private Integer weight = 0; private String color = ""; public Apple(Integer weight, String color) { this.weight = weight; this.color = color; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String toString() { return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}'; } }
这仅仅是一个普通的实体类。然后我们有一组这样的对象,在实现中需要针对这些Apple对象的weight属性进行排序。这是一个非常简单的问题,一种最传统的方式无非就是实现一个Comparator<Apple>的接口,再将该实现的对象作为参数传递到原来的sort方法中去。其详细的实现如下:
AppleComparator:
import java.util.Comparator; public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2) { return a1.getWeight().compareTo(a2.getWeight()); } }
主函数代码如下:
import java.util.*; public class Sorting { public static void main(String[] args) { List<Apple> inventory = new ArrayList<>(); inventory.addAll(Arrays.asList(new Apple(80, "green"), new Apple(155, "green"), new Apple(120, "red"))); inventory.sort(new AppleComparator()); System.out.println(inventory); } }
这样,我们就实现了一个基于自定义对象进行排序的功能。它能够实现排序的要点是inventory.sort方法里需要接收的参数是Comparator类型的对象。而这个类型的对象必须要实现compare方法。从功能实现的角度来说,我们的方法需要传递的参数类型和实际传递的都是对象,也正好符合一切皆对象的这个说法。
当然,这种实现方式显得比较繁琐,因为我们这里仅仅是需要实现一个简单的接口,这里却需要定义一个类,专门实现它。而且真正能够在排序里起作用的就是compare这个方法。只有根据它才能知道怎么排序。可是在这里没办法,必须针对这个需要的方法行为包装成一个对象传递过去。
当然,我们还想到一种稍微简单一点的方法,就是使用匿名类,这种实现的方式如下:
import java.util.*; public class Sorting { public static void main(String[] args) { List<Apple> inventory = new ArrayList<>(); inventory.addAll(Arrays.asList(new Apple(80, "green"), new Apple(155, "green"), new Apple(120, "red"))); inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2) { return a1.getWeight().compareTo(a2.getWeight()); } }); System.out.println(inventory); } }
这种方式实现的代码要稍微简洁一点,但还是显得比较冗长。如同前面我们讨论中提到,对这个接口建模最关键的就是它的这个compare方法。至于其传递方式,由于要求面向对象设计的要求,必须将该方法包装在一个对象里。那么,有没有更加简洁的方式来解决这个问题呢?
lambda表达式
在详细讨论lambda表达式之前,我们先看看用这种方式来解决上述问题有多简单。我们实现排序比较的代码如下:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
我们甚至可以将类型信息给省略掉:
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
甚至更简单的情况下可以用如下代码来描述:
inventory.sort(comparing(Apple::getWeight));
在上述代码里,我们没有新建什么对象,而是采用一种类似于方法传递的方式来实现排序的目的。这里,我们使用的就是lambda表达式。在函数式编程语言的概念里,这里相当于将一个函数作为参数传递到另外一个对象方法里。笼统的来说,在java8里新加入的特性是的我们可以将一个函数作为参数来传递了。
那么,该怎么来理解lambda表达式呢?一个lambda表达式可以视为一个匿名方法,但是它可以像普通的对象参数那样被传递。它没有具体定义的名字,但是可以有一组参数,函数体以及返回类型。它甚至可以包含有被抛出的异常列表。我们针对它的每个具体特征来讨论。
匿名(Anonymous)
像在前面的代码里,我们传递给inventory.sort方法的是一段代码。这段代码没有方法声明。
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight())
函数式(Function)
在上述传递的函数里,这个函数的定义和使用和我们通常使用的方法不一样。它并不是专门定义在某个类里面。但是它却有我们在普通类里定义的方法具有的特性。比如函数参数列表,返回值以及抛出的异常列表。
可传递性(Passed around)
从往常的理解来看,一个函数的定义是放在某个类里面的。但是这里它却表现的像一个类一样。实际上我们甚至可以将它赋值给一个变量。比如上述示例里的代码可以声明如下:
Comparator<Apple> comparator = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); inventory.sort(comparator);
从代码的表面上看起来,我们可以用一个lambda表达式来替换一个对应的接口。关于这方面的详情将在后面讨论。
lambda表达式语法
从前面示例我们可以看到一个lambda表达式的样式基本如下:
它不需要定义名字,所以首先是包含有输入的函数参数列表,一般用一个括号来包含,比如:()
然后是一个箭头符号,后面包含具体的函数语句,可以有一句或者多句。比如: a.length - b.length。
所以上述示例的解析结构如下图:
基于上述的描述,我们可以定义很多类似的lambda表达式,它们对应的函数签名如下:
(String s) -> s.length
输入参数为String类型,返回结果为int类型的函数。它的函数签名样式为: (String) -> int
(Apple a) -> a.getWeight() > 150
输入参数为Apple类型,返回结果为boolean类型的函数,函数签名样式为:(Apple) -> boolean
在需要返回结果的函数签名里,如果函数语句只有一句的话,该语句执行的结果将作为函数结果返回。
(int i, int j) -> { System.out.println(i); System.out.println(j); System.out.println("Result printed"); }
输入参数为int, int,返回结果为void,即没有返回任何结果。函数签名样式为:(int, int) -> void
(String a, String b) -> { System.out.println(a); return a + b; }
该示例代码的输入参数为(String, String),返回结果为String。但是因为函数中有多条语句,所以需要添加一个return语句在最后作为返回的结果。
看了上述简单的介绍后,估计我们心里还是有很多的疑问。比如说,为什么上述的代码可以对等的替换一个接口呢?难道说它和一个接口是等价的?那么它是不是可以替换所有类型的接口呢?另外一个就是,我们前面写的lambda表达式里,对于传入的参数甚至连类型都没有声明,它是怎么知道我们的参数类型的呢?
函数式接口(Functional interfaces)
在前面的示例代码里,我们看到,可以将一个lambda表达式用在一个接口所使用的地方。在java8里,lambda表达式可以传递和识别的类型是函数式接口。那么函数式接口是什么呢?
在java里,我们经常可以看到不少只包含有一个方法定义的接口,比如Runnable, Callable, Comparator等。而这种仅仅包含有一个接口方法的接口就可以称其为函数式接口。需要特别注意的一点就是,这里指的方法是接口里定义的抽象方法。由于java8里引入了默认方法(default method),在接口里也可以定义默认方法的实现。但是这些方法并不算抽象方法。关于默认方法我们会在后续的文章里讨论。
另外,如果某个接口定义了一个抽象方法的同时继承了一个包含其他抽象方法的接口,那么该接口就不是函数式接口。实际上,如果我们去查看目前那些常见的java类库里的函数式接口,它们都有一个如下的声明修饰: @FunctionalInterface。比如Comparator接口和Runnable接口:
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); // details ignored. }
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }在这里@FunctionalInterface相当于函数式接口的声明,类似于我们继承类里实现某个方法使用的@Override声明。它表示该接口是函数式接口,方便在编译的时候进行检查。
这样,我们可以发现,每个函数对象对应一个函数式接口的实例。所有传递单个方法接口的地方就可以用lambda表达式来替换了。
除此之外,Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:
- Predicate<T>——接收T对象并返回boolean
- Consumer<T>——接收T对象,不返回值
- Function<T, R>——接收T对象,返回R对象
- Supplier<T>——提供T对象(例如工厂),不接收值
- UnaryOperator<T>——接收T对象,返回T对象
- BinaryOperator<T>——接收两个T对象,返回T对象
目标类型(Target typing)
在前面的讨论中我们发现,其实一个lambda表达式就是一个对应的函数式接口对象。但是,一个lambda表达式它本身并没有包含它到底实现哪个函数式接口的信息。我们怎么知道我们定义的某个lambda表达式可以用到某个函数式接口呢?实际上,对于lambda表达式的类型是通过它的应用上下文来推导出来的。这个过程我们称之为类型推导(type inference)。那么,在上下文中我们期望获得到的类型则称之为目标类型。该怎么来理解上述的内容呢?
例如,下面代码中的lambda表达式类型是ActionListener:
ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers());
但是同样的lambda表达式在不同的上下文中可以有不同的类型:
Callable<String> c = () -> "done"; PrivilegedAction<String> a = () -> "done";
第一个lambda表达式() -> "done"是Callable的实例,而第二个lambda表达式则是PrivilegedAction的实例。
下面,我们来结合前面的示例代码做一个详细的类型检查分析:
首先,我们这部分应用lambda表达式的代码如下:
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
1. 我们首先检查inventory.sort方法的签名,它的详细签名如下:void sort(Comparator<? super E> c)。
2. 那么它期待的参数类型是Comparator<Apple>.
3. 我们来看Comparator接口,它是一个函数式接口,并有定义的抽象方法compare。
4. 这个compare方法的详细签名如下:int compare(Apple o1, Apple o2),这表示这个方法期待两个类型为Apple的输入参数,并返回一个整型的结果。
5. 比对lambda表达式的函数签名类型,它也是两个输入类型为Apple,并且输出为int类型。
这样,lambda表达式的目标类型和我们的类型匹配了。
总结起来,当且仅当下面所有条件均满足时,lambda表达式才可以被赋给目标类型T:
- T是一个函数式接口
- lambda表达式的参数和T的方法参数在数量和类型上一一对应
- lambda表达式的返回值和T的方法返回值相兼容(Compatible)
- lambda表达式内所抛出的异常和T的方法throws类型相兼容
由于目标类型(函数式接口)已经“知道”lambda表达式的形式参数(Formal parameter)类型,所以我们没有必要把已知类型再重复一遍。也就是说,lambda表达式的参数类型可以从目标类型中得出:
Comparator<Apple> comp = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
在上面的例子里,编译器可以推导出a1和a2的类型是Apple。所以它就在lambda表达式里省略了a1, a2的类型声明。这样可以使得我们的代码更加简练。
方法引用(Method references)
我们定义一些lambda表达式并传递给一些函数,这种方式可以使得我们实现的代码很简练。但是在有的情况下,我们已经有一些方法实现同样的功能了,那么我们能不能想办法重用这些原有的功能而不至于自己去重复实现呢?
像我们前面代码示例里使用的如下代码:
inventory.sort(comparing(Apple::getWeight));这里就是引用了一个方法。将它作为一个参数传递给comparing方法。这里的Apple::getWeight可以看做lambda表达式p -> p.getWeight()的一个简写形式。其中Apple::getWeight就是一个对Apple类中实现方法getWeight的引用。所以,我们可以将方法引用当做lambda表达式的语法糖。
方法引用有很多种,它们的语法如下:
- 静态方法引用:ClassName::methodName
- 实例上的实例方法引用:instanceReference::methodName
- 超类上的实例方法引用:super::methodName
- 类型上的实例方法引用:ClassName::methodName
- 构造方法引用:Class::new
- 数组构造方法引用:TypeName[]::new
对于静态方法引用,我们需要在类名和方法名之间加入::分隔符,例如Integer::sum。
对于具体对象上的实例方法引用,我们则需要在对象名和方法名之间加入分隔符:
Set<String> knownNames = ... Predicate<String> isKnown = knownNames::contains;
相关推荐
java8lambda表达式的安卓Studio工程Demo,供初步学习使用
Java8的lambda表达式
java lambda表达式,lambda 表达式基本上表达了函数式接口的实例(具有单一抽象方法的接口称为函数式接口。一个例子是 java.lang.Runnable)。lambda 表达式实现了唯一的抽象函数,因此实现了函数式接口 lambda ...
JAVA 8 Lambda表达式-Lambda Expressions.rar
这个是专门介绍java 8 lambda表达式的一本书,目前没有中文版的,这个是英文版的。
主要内容: ● 为何需要lambda,...第2章 Java lambda表达式的基础知识 23 第3章 流与管道介绍 55 第4章 终止流:收集与汇聚 91 第5章 起始流:源与分割迭代器 135 第6章 流的性能 167 第7章 使用默认方法演化API 195
说明:本文档主要讲解java8中的Lambda表达式。内容完全基于java 8 tutorial,加上一些自己的注释与理解。使用代码本身来进行解释(这是java 8 tutorial中的风格),同时去掉一些无关紧要的知识点(比如泛型等),...
4、函数式接口使用:学习如何使用Lambda表达式与Java中的函数式接口进行交互,包括传递函数、使用函数式接口的默认方法和方法引用。 本源码资源旨在帮助用户掌握以下几个方面: 1、Lambda表达式概述:了解Lambda...
java8 lambda表达式在集合中的使用,包含代码例子。
java lambda 表达式中文详解(语言篇和类库篇),更方便更快捷了解java lambda表达式
Java 1.8 lambda表达式示例源码
关于Java中lambda的表达式,Java多核编程,清华大学(出版)
lambda表达式是JAVA8中提供的一种新的特性,它支持Java也能进行简单的“函数式编程”。 下面这篇文章主要给大家介绍了关于Java8新特性Lambda表达式的一些复杂用法的相关资料,需要的朋友可以参考借鉴,下面来一起看...
视频地址:https://www.bilibili.com/video/BV1ut411g7E9 【Java8】Lambda表达式 和 Stream API 详解笔记 md文档
Java8发布到现在至少3年了,但是对Lambda表达式不熟悉、看不懂、不会用的现象非常常见。 即使是升级到JDK1.8了,但是很多开发者依然是停留在1.8之前的开发方式,使用的也是非常老旧和过时的API,遇到函数式接口也是...
Stream、Lambda表达式练习.doc
精通lambda表达式:Java多核编程,使用lambda表达式和流的最佳实践
Java 8 在 2013 年发布,Java 8 将支持 Lambda 功能,尽管该规范还在不断的变化,但是 Java 8 的开发版已经实现了对 lambda 的支持。
支持java8 lambda表达式的class反编译,亲测可用。更高版本jdk需自行下载测试。 使用步骤:beyond compare的菜单--工具(tools)--- 导入设置(import settings)导入即可. 将jar包或class文件拖放对比的时候,在...