Lambda表达式

Lambda表达式

第一节 引文

  Java是一种面向对象语言,在Java中传递代码段并不简单,不能直接进行传递,而要通过构造对象,对象的类中要有方法可以容纳所需代码。所以在Java8之后,加入了Lambda表达式来解决这一问题。

  简单的理解,Lambda表达式就是表示可传递的匿名函数的一种形式。没有名称,但有参数列表、函数主体、返回类型,可能还会有一个可以跑出的异常列表。

  在编程中会经常用到事件处理器和回调函数,即注册一个对象,在事件发生时调用指定方法。Lambda可以简明的把代码或方法作为参数传递进去执行,比如一些场景如:加入作为比较的代码来简单的参数化一个排序方法,或是利用新的Stream API在一组数据上进行复杂的查询指令。

  通过Lambda表达式我们可以解决很多常见的问题,如有一个学生类,需要查询出所有男生,Java实现我们需要编写filterStudentBoy()方法,然后又有了新需求查询所有年龄超过18岁的学生,我们又要复制代码,把遍历时条件判断修改为判断年龄。而Lambda表达式可以将条件代码当作参数传递给方法,这样我们就不用重复的实现一个个filter,只需要一个Predicate参数,实现一个filter方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Student {
private String id;
private String name;
private int age;
private String sex;

static List<Student> filter(List<Student> students, Predicate<Student> predicate){
List<Student> result = new ArrayList();
for(Student student : students){
if(predicate.test(student)){
result.add(student);
}
}
return result;
}

//实现判断函数
public static boolean isStudentMan(Student student){
return "man".equals(student.getSex());
}
public static boolean isStudentOver18(Student student){
return 18 < student.getAge();
}

public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("001","张三",19,"man"));
students.add(new Student("002","李四",17,"man"));
students.add(new Student("003","王五",17,"man"));
students.add(new Student("004","马六",18,"man"));
students.add(new Student("005","兰兰",16,"woman"));
students.add(new Student("001","花花",19,"woman"));

Predicate<Student> conditionMan = Student::isStudentMan;
List<Student> resultMan = filter(students,conditionMan);
print(resultMan);
List<Student> resultWoMan = filter(students,conditionMan.negate());
print(resultWoMan);

List<Student> resultOver18 = filter(students,Student::isStudentOver18);
print(resultOver18);

//为需求实现一堆判断函数太繁琐了,可以直接通过匿名函数或Lambda来实现
List<Student> resultManLambda = filter(students,(Student student) -> "man".equals(student.getSex()));
print(resultManLambda);
List<Student> resultOver18Lambda = filter(students,(Student student) -> 18 < student.getAge());
print(resultOver18Lambda);

//Stream API实现筛选和转换
List<Student> resultManLibrary = students.stream().filter((Student student) -> "man".equals(student.getSex())).collect(Collectors.toList());
print(resultManLibrary);
}

......
}

  执行结果如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
id: 001name: 张三age: 19sex: man
id: 002name: 李四age: 17sex: man
id: 003name: 王五age: 17sex: man
id: 004name: 马六age: 18sex: man
------
id: 005name: 兰兰age: 16sex: woman
id: 001name: 花花age: 19sex: woman
------
id: 001name: 张三age: 19sex: man
id: 001name: 花花age: 19sex: woman
------
id: 001name: 张三age: 19sex: man
id: 002name: 李四age: 17sex: man
id: 003name: 王五age: 17sex: man
id: 004name: 马六age: 18sex: man
------
id: 001name: 张三age: 19sex: man
id: 001name: 花花age: 19sex: woman
------

Predicate,谓词,在数学上用来表示一个类似函数的东西,接受一个参数值,并返回true或false。相比Function<Student, Boolean>,Predicate更标准,效率也更高,因为不用多封装一层Boolean。

  Java通用库可以提供了一系列filter方法就足以满足类似需求,但为了更好的利用并行,Java 8采用Stream API来进行转换和筛选。

1
2
3
4
static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);

//Stream API实现筛选和转换
List<Student> resultManLibrary = students.stream().filter((Student student) -> "man".equals(student.getSex())).collect(Collectors.toList());

第二节 函数

  编程语言中的函数一词通常指方法,特别是静态方法;隐含着没有副作用的意义。Java 8新增了函数作为值的新形式,这有助于流,并使Java可以进行多核处理器上的并行编程。

  Java中都有哪些值?原始值,如基本数据类型的32、3.14等;对象值,严格说是对象的引用。获取对象的途径可以是new、工厂方法或库函数等,对象的引用指向类的一个实例。编程的目的就是操作值,按照这个传统可以定义这些值是一等值,语言中的其他结构可能有助于我们来表示值得结构,但在程序运行期间无法传递,所以可以定义为二等值。类可以实例化来产生值,但类和方法都不是值,但在运行时传递方法可以把方法变为一等值,这个功能后来被证明对编程非常有用,甚至一些语言如Smalltalk和JavaScript也在探索把类等二等值也变为一等值的方法。

方法和Lambda作为一等值

  假设场景如下:需要筛选一个目录中的所有隐藏文件。我们用FileFilter来做筛选,改写accept()方法,使其判断文件是否隐藏。虽然只有几行代码,但仍显得十分繁琐,为什么有了isHidden()还需要把它包在FileFilter的实例化对象中呢?

1
2
3
4
5
File[] hiddenFiles = new File(".").listFiles(new FileFilter(){
public boolean accept(File file){
return file.isHidden();
}
});

  在Java 8之后,可以重写上述代码如下。方法不再是二等值了,与用对象引用传递对象类似,通过File::isHidden创建了一个方法引用,并作为值传递给listFiles()。

1
File[] hiddenFiles = new File(".").listFiles(File::isHidden);

第三节 语法

(parameters) -> expression

(parameters) -> { statements; }

  (int x) -> x + 1这种匿名函数表示调用时给定参数x,就返回x+1值。Lambda表达式有三个部分:(int x)是参数列表,->是箭头,用来分隔参数和主体,x + 1就是Lambda主体。Lambda表达式不用return,因为已经隐含了return,当然也可以显示的写明return。lamba表达式可以看作是一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//返回一个int
(String first,String second) -> first.length() - second.length()

//主体可以是代码块
(String first,String second) -> {
if(first.length() < second.length()) return -1;
else if(first.length() > second.length()) return 1;
else return 0;
}

//void,没有返回值
(int x,int y) -> {
System.out.print("result = ");
System.out.print(x+y);
}

//没有参数,返回int
() -> 42

Comparator<String> comp = (first,second) -> first.length() - second.length()

  如果表达式没有参数也要带(),但若可以推导出参数类型也可以忽略其类型。如果方法只有一个参数,可以推导出其类型,可以省略括号()。无需指定lambda表达式的返回类型,其总会根据上下文推导出返回类型。

1
2
3
4
String[] list = new String[]{"caaa","as","b"};
print(list);
Arrays.sort(list,(first,second) -> first.length() - second.length());
print(list);

  输出结果如下。

1
2
caaa as b 
b as caaa

  一些只用一次的代码段使用匿名函数可以更清晰和简单,但如果代码段很长还是最好通过方法引用来实现,代码清晰和易于理解是优先级更高的事情。


第四节 函数式接口

  当接口只有一个抽象方法,实例化此接口的对象时,可以用lambda表达式,此种接口叫函数式接口。接口可以定义默认方法,但只要它只定义了一个抽象方法,就仍是一个函数式接口。

接口并非都是抽象方法,也可以声明非抽象方法

  Comparator和Predicate就是此类接口。Arrays.sort方法第二个参数需要Comparator实例,实现了Comparator的某个类的对象,在此对象上调用compare方法就会执行Lamba表达式的体。Lamba表达式目前只能用来转换函数式接口,这是设计者避免语言变的更复杂而做的选择。

1
public static <T> void sort(T[] a, Comparator<? super T> c)

@FunctionInterface注解标识接口被设计为函数式接口,如果被标识接口非函数式接口,编译器会返回一个错误。

函数描述符

  函数式接口的抽象方法的签名叫函数描述符(签名:方法名+形参列表+返回值,唯一确定一个方法)。

  为了应对不同的Lambda表达式,我们需要一套能够描述常见的函数描述符的函数式接口。java.util.function包内定义了很多函数式接口,其中比如BiFunction可以保存上述字符串比较Lamba表达式。但变量comp并不能直接在Arrays.sort方法中代替Lamba表达式。

1
BiFunction<String,String,Integer> comp = (first,second) -> first.length() - second.length();

  如果想要用函数式接口变量传递Lamba表达式,还是要在函数时声明特定的函数式接口,如ArrayList的removeIf方法。

1
2
3
4
5
6
7
8
arrayList.removeIf(e -> e == null);

public boolean removeIf(Predicate<? super E> filter) {
return removeIf(filter, 0, size);
}

Predicate notnull = e -> e == null;
arrayList.removeIf(notnull);

Predicate

  Predicate定义了test抽象方法,接收一个T对象,返回一个Boolean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

default Predicate<T> negate() {
return (t) -> !test(t);
}

default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

Consumer

  Consumer定义了accept抽象方法,接收一个T对象,没有返回值。

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);

default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

Function

  Function<T, R>定义了apply抽象方法,接收一个T对象,返回一个泛型R的对象。很适合流程结束要进行数据类型转换的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
return t -> t;
}
}

针对原始类型的特化

  Java提供了一些针对原始类型的函数式接口,比如想要避免自动装箱,可以用IntPredicate代替Predicate。

异常

  Lambda表达式抛出异常有两种办法:1.定义一个函数式接口,声明受检异常。2.把Lambda表达式包在一个try/catch块中。

总结

  Java 8中常用的函数式接口,如下。

函数式接口 函数描述符 原始类型特化
Predicate T->boolean IntPredicate,
LongPredicate,
DoublePredicate
Consumer T->void IntConsumer,
LongConsumer,
DoubleConsumer
Function<T,R> T->R IntFunction,
IntToDoubleFunction,
IntToLongFunction,
LongFunction,
LongToDoubleFunction,
LongToIntFunction,
DoubleFunction,
ToIntFunction,
ToDoubleFunction,
ToLongFunction
Supplier ()->T BooleanSupplier,
IntSupplier,
LongSupplier,
DoubleSupplier
UnaryOperator T->T IntUnaryOperator,
LongUnaryOperator,
DoubleUnaryOperator
BinaryOperator (T,T)->T IntBinaryOperator,
LongBinaryOperator,
DoubleBinaryOperator
BiPredicate<L,R> (L,R)->boolean
BiConsumer<T,U> (T,U)->void ObjIntBiConsumer,
ObjLongBiConsumer,
ObjDoubleBiConsumer
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>,
ToLongBiFunction<T,U>,
ToDoubleBiFunction<T,U>

  Java API提供了一些比较常用的函数式接口

函数式接口 参数类型 返回类型 抽象方法名 描述 其它方法
Runnable void run 作为无参数或返回值的动作运行
Supplier T get 提供一个T类型的值
Consumer T void accept 处理一个T类型的值 andThen
BigConsumer<T,U> T,U void accept 处理T和U类型的值 andThen
Function<T,R> T R apply 有一个T类型参数的函数 andThen,
compose,
identity
BigFunction<T,U,R> T,U R apply 有T和U类型参数的函数 andThen
UnaryOperator T T apply 类型T上的一元操作符 andThen,
compose,
identity
BinaryOperator T,T T apply 类型T上的二元操作符 andThen,
maxBy,
minBy
Predicate T boolean test 布尔值函数 and,
or,
negate,
isEqual
BigPredicate<T,U> T,U boolean test 有两个参数的布尔值函数 and,
or,
negate

  Lambda及函数式接口的例子,如下。

使用案例 Lambda例子 对应的函数式接口
布尔表达式 (List list) -> list.isEmpty() Predicate<List>
创建对象 () -> new Apple(10)) Supplier
消费一个对象 (Apple a) ->
System.out.println(a.getWeight())
Consumer
从一个对象中选择/提取 (String s) -> s.length() Function<String,Integer>
ToIntFunction
合并两个值 (int a, int b) -> a * b IntBinaryOperator
比较两个对象 (Apple a1, Apple a2) ->
a1.getWeight().compare(a2.getWeight())
Comparator
BiFunction<Apple, Apple, Integer>
ToIntBiFunction<Apple, Apple>

  Lambda表达式的特点是延迟执行,为什么要延迟执行呢?

  1. 需要在独立的线程中运行。
  2. 需要多次运行代码。
  3. 在算法的适当位置执行代码,比如排序算法的比较操作。
  4. 在某些情况下执行代码,如点击,数据传输等。
  5. 只在必要时才运行代码。

  假设需要需要重复一个动作N次,并在每次告知其迭代次序。

1
2
3
4
5
6
public static void repeat(int n,IntConsumer action){
for(int i = 0;i < n;i++)
action.accept(i);
}

repeat(10,i -> System.out.println("CountDown: " + (9 - i)));

  结果如下。

1
2
3
4
5
6
7
8
9
10
CountDown: 9
CountDown: 8
CountDown: 7
CountDown: 6
CountDown: 5
CountDown: 4
CountDown: 3
CountDown: 2
CountDown: 1
CountDown: 0

第五节 类型检查、类型推断以及限制

类型检查

  Lambda的类型由上下文(目标类型)推断而出,如接受它传递的方法的参数、接受它的值得局部变量。

1
2
3
4
5
6
7
8
9
10
List<Apple> apples = filter(inventory, (Apple a) -> a.getWeight() > 150);
//Lambda表达式的类型检查过程如下

filter(List<Apple> inventory, Predicate<Apple> p);
//由方法定义可以得到目标类型为Predicate<Apple>,T绑定到Apple

boolean test(Apple apple);
//由此抽象方法可以知道是Apple -> boolean

//函数描述符Apple -> boolean符合Predicate<T>的签名T->boolean,类型检查无误。
  1. 找出filter方法的声明。
  2. 要求其是Predicate对象的第二个正式参数
  3. Predicate是一个函数式接口,定义了一个test抽象方法。
  4. test方法描述了一个函数描述符,可以接受一个Apple,并返回一个boolean。
  5. 确认filter的实际参数是否匹配要求。

  如果Lambda表达式抛出异常,抽象方法必须声明了此异常。

  因为目标类型的概念,同一个Lambda表达式可以对应多个不同的函数式接口

1
2
3
4
5
6
7
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;

//Predicate返回一个boolean
Predicate<String> p = s -> list.add(s);
//Consumer返回void
Consumer<String> b = s -> list.add(s);

只要Lambda表达式主体是一个语句表达式,其就和void函数描述符兼容。

类型推断

  Java编译器可以从上下文推断出需要用什么函数式接口来配合Lambda表达式,意味着它可以推断出Lambda的签名,因为函数描述符可以通过目标类型得出。所以编译器可以得到Lambda表达式的参数类型,而无需显示的标注。

1
2
3
4
5
//参数a没有显示类型,类型推断
List<Apple> apples = filter(inventory, a -> a.getWeight() > 150);

//无类型推断
List<Apple> apples = filter(inventory, (Apple a) -> a.getWeight() > 150);

局部变量的限制

  Lambda表达式可以使用外部的变量,即自由变量,叫做捕获Lambda

1
2
int portNum = 1337;
Runnable r = () -> System.out.println(portNum);

  Lambda可以没有限制地捕获实例变量和静态变量,但局部变量必须显示的声明为final或事实上是final。也就是说Lambda只能捕获一次局部变量,捕获实例变量可以看作是捕获最终局部变量this。

1
2
3
int portNum = 1337;
Runnable r = () -> System.out.println(portNum);//异常
portNum = 1337;

限制地原因有两个:

  1. 线程不安全。实例变量存储在堆中,而局部变量存储在栈上。若Lambda可以直接访问局部变量,可能会出现分配变量的线程收回变量后Lambda所在线程仍去访问变量的情况。所以Java访问局部变量时,实际是在访问其副本,如果变量值只被赋予一次那就没什么区别了。
  2. 不适合并行处理。这个限制是为了不鼓励使用改变外部变量的典型命令式编程模式,这种模式不利于并行处理。

第六节 方法引用

方法引用

  方法引用就是用 :: 操作符分割方法名和对象或类名。相比Lambda表达式,方法引用更加简洁。如果Lambda表达式只代表“直接调用这个方法”,那就可以用方法引用来代替。

1
2
3
4
5
//Lambda表达式
apples.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

//方法引用
apples.sort(Comparator.comparing(Apple::getWeight));

  目标引用在::前,方法名则在::后。

方法引用有三类:

  1. 指向静态方法的方法引用:如Integer::parseInt等。
  2. 指向任意类型实例方法的方法引用:如String::length等。
  3. 指向现有对象的实例方法的方法引用:如有一个局部变量exp,用于存放Transaction类型对象,支持实例方法getValue,可以如此写exp-Transaction::getValue。
1
2
3
4
5
6
Class::staticMethod == (x,y) -> Class.staticMethod(x,y) //第一种
Class::instanceMethod == (x,y) -> x.instanceMethod(y) //第二种
object::instanceMethod == (x,y) -> object.instanceMethod(x,y) //第三种

System.out::println == (String s) -> System.out.println(s)
String::substring == (string,i) -> str.substring(i)

为三种不同类型的Lambda表达式构建方法引用的方法

  除了以上三种引用,其实还有构造函数、数组构造函数和父类调用这些特殊的方法引用。

构造函数引用

  构造器引用和方法引用的区别就是用new关键字代替方法名。

  构造器引用这种不实例化构造函数却可以引用它的方式可以产生一些有趣的应用。比如将一批构造函数对应的函数式引用存储到Map中,可以写一个函数根据不同条件选择不同的构造函数创建对象。

1
2
3
4
5
6
7
8
9
10
ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);

Person::new == () -> new Person()

//无参构造器
Supplier<Apple> c1 = Apple::new;
//构造器Apple(int weight)
Function<Integer, Apple> c2 = Apple::new;

数组构造函数引用

  Java无法构造泛型T数组,而数组构造函数引用可以跳过这个限制。

1
2
3
4
5
6
7
8
//数组构造函数引用
int[]::new == x -> new int[x]

T[] list = new T[n];//Error

Object[] list = stream.toArray();//需要Person数组而不是Object

Person[] people = stream.toArray(Person[]::new);//解决问题

第七节 变量作用域

lambda表达式包括三个部分:

  1. 一个代码块
  2. 参数
  3. 自由变量的值,即非参数且不在代码内定义的变量。

  lambda表达式的数据结构需要存储自由变量的值,如下代码中start是外部方法的局部变量,可以直接调用或叫捕获外围作用域的变量,但限制是所捕获的值要明确定义,只能引用不会改变的变量,这是因为若在lambda表达式中改变变量值,并发执行多个动作时就会导致线程不安全,且此变量也不能在外围被修改,所以lambda表达式要引用的变量应该是final修饰的最终变量

1
2
3
4
5
6
7
public static void countDown(int start,int delay){
ActionListener listener = event -> {
System.out.println(start);
start--;//Error
};
new Timer(delay,listener).start();
}

  lambda表达式的体与嵌套块具有相同的作用域,所以命名冲突和遮蔽规则与后者相同。

  lambda表达式内的this指向创建此表达式的方法的this参数,也就是实例对象。


第八节 使用Lambda表达式

  实现一个为苹果排序可选不同策略的需求。

  Java API已提供了可用的排序方法sort,所以我们省去了实现排序方法的时间。而我们要做的是把排序的策略传递给sort方法,我们可以说sort的行为被参数化了。

1
void sort(Comparator<? super E> c);

  旧版本的思路下可能就会这样实现。

1
2
3
4
5
6
public class AppleWeightComparator implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}

  以下代码展示了我们一步步优化得到最终解决方案的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Test {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple(160,"green"));
apples.add(new Apple(120,"red"));
apples.add(new Apple(170,"green"));

//第一种实现,行为参数化
apples.sort(new AppleWeightComparator());
apples.forEach(System.out::println);
//sort行为被参数化了,根据策略的不同,排序结果也有相应的变化,但每一种策略都需要构建对应的Comparator
System.out.println();

//第二种实现,引入匿名类
apples.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
//匿名类不需要构建类,但本质上仍是啰嗦的

//第三种实现,引入Lambda
apples.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
//需要函数式接口时可以提高Lambda表达式,而Comparator<? super E>的函数描述符是(T, T) -> int,所以我们传递(Apple, Apple) -> int

//第四种实现,引入库提供的辅助方法
apples.sort(Comparator.comparing((Apple a) -> a.getWeight()));

//第五种实现,引入方法引用
apples.sort(Comparator.comparing(Apple::getWeight));
}
}

第九节 复合Lambda表达式

  许多函数式接口都提供了进行复合的方法,我们可以组合多个简单的Lambda成一个复杂的表达式。

比较器复合

  逆序:如果你想要一个相反的排序策略,只需要调用Comparator的默认方法**reversed()**即可。

  比较器链:如果两个苹果重量相同,你可能会需要进一步定义优先级。**thenComparing()方法和comparing()**一样可以接收函数。

1
2
3
4
5
6
7
apples.sort(Comparator.comparing(Apple::getWeight));
//逆序
apples.sort(Comparator.comparing(Apple::getWeight).reversed());
//比较器链
apples.sort(Comparator.comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getColor));

谓词复合

  谓词接口提供了三个默认方法:negate、and、or。

  • negate(): 用来返回一个Predicate的非;
  • and(): 用来返回一个Predicate的交;
  • or(): 用来返回一个Predicate的并;
1
2
3
4
5
6
7
8
Predicate<Apple> redApple = a -> "red".equals(a.getColor());
//negate得到非
Predicate<Apple> notRedApple = redApple.negate();
//and得到交
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
//or得到并
Predicate<Apple> redAndHeavyOrGreenApple = redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));

  and、or方法是根据从左到右顺序决定优先级,所以a.or(b).and(c)可以看作(a || b) && c。

函数复合

  Function接口提供了两个默认方法:andThen、compose。

  • andThen(): 返回一个函数,先对输入应用一个给定的函数,再对输出应用另一个函数;
  • compose(): 先把给定函数用作compose参数所提供的函数,再把函数本身用于结果;
1
2
3
4
5
6
7
8
9
10
11
12
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;

//andThen,先应用前一个函数,再应用后一个函数
Function<Integer, Integer> h = f.andThen(g);//数学表示:g(f(x))或(g o f)(x)
int result1 = h.apply(1);
System.out.println(result1);//输出4

//compose,先应用后一个函数,再应用前一个函数
Function<Integer, Integer> i = f.compose(g);//数学表示:f(g(x))或(f o g)(x)
int result2 = i.apply(1);
System.out.println(result2);//输出3

andThen和compose


第十节 数学上的类似思想(扩展)

举例-积分

  假设我们要求函数:f(x)=x+10在x处于(3,7)间的面积大小。因为函数是直线,所以可以根据梯形面积公式计算:1/2 * ((3+10) + (7+10)) * (7-3) = 60。

函数f(x)=x+10

  对于Java实现,我们可能第一步是想用编程语言替换数学符号。所以我们设计一个函数integrate(f, 3, 7),f表示函数参数。

  有了Lambda表达式,我们可以这样写:integrate((double x) -> x + 10, 3, 7) 或方法引用:integrate(C::f, 3, 7)。

  我们可以实现integrate,代码如下。

1
2
3
public double integrate(DoubleFunction<Double> f, double a, double b){
return (f.apply(a) + f.apply(b)) * (b - a) / 2.0;
}

参考博客和文章书籍等:

《Java核心技术 卷Ⅰ》

《Java8实战》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容