Java·Lambda表达式

第一个Lambda表达式

先来看一段根据字符串长度排序的Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abcd","abc"};
Arrays.sort(s1, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(Arrays.toString(s1));
}
}

输出结果

[a, ab, abc, abcd]

如果我们用lambda表达式取而代之:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abcd","abc"};
Arrays.sort(s1,(String o1, String o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(s1));
}

}

输出结果

[a, ab, abc, abcd]

可以发现简单许多,而其中的Arrays.sort(s1,(String o1, String o2) -> o1.length() - o2.length());就是我们的第一个lambda表达式。

常用的函数式接口

Predicate

java.util.function.Predicate<T>定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。在你需要涉及类型T的布尔表达式时,就可以使用这个接口。直接上代码。

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
package com.xzc;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class Test {
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> results = new ArrayList<T>();
for(T l : list){
if(p.test(l)){
results.add(l);
}
}
return results;
}
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("abcd");
l.add("");
l.add("abc");
System.out.println(l);
List<String> l1 = filter(l,(String s) -> (!s.isEmpty()));
System.out.println(l1);
}
}

输出结果:

[abcd, , abc]
[abcd, abc]

我们这里用泛型方法传入String类的List,对其中进行非空字符串判断,然后返回一个String类的List,其中剔除了空字符串。这里我们就是把进行判断的boolean函数进行参数化,然后在执行的时候将行为(这里是判断字符串非空)当做参数传入,我们这里的lambda表达式覆写了test方法。

Consumer

java.util.function.Consumer<T>定义了一个名为accept的抽象方法,它接受泛型T的对象,没有返回。如果你要访问T的对象,并对它执行某些操作,就可以用这个借口。直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.xzc;


import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;


public class Test {
public static <T> void forEach(List<T> list, Consumer<T> c){
for (T l : list){
c.accept(l);
}
}
public static void main(String[] args) {
forEach(Arrays.asList(1,2,3,4,5),(Integer integer) -> System.out.println(integer));
}

}

输出结果

1
2
3
4
5

我们这里就是把参数化的行为accept,用lambda表达式进行覆写,覆写为打印list的每个对象。

Function

java.util.function.Function<T,R>接口定义了一个叫做apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象,如果你需要定义一个lambda,将输入对象的信息映射到输出,可以使用这个接口,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.xzc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;


public class Test {
public static <T,R> List<R> map(List<T> list, Function<T,R> f){
List<R> result = new ArrayList<>();
for(T l : list){
result.add(f.apply(l));
}
return result;
}
public static void main(String[] args) {
List<Integer> l = map(Arrays.asList("lambda","in","actions"),(String s)->s.length());
System.out.println(l);
}
}

输出结果:

[6, 2, 7]

这里我们对泛型T返回泛型R传入了String返回Integer,即覆写apply方法为传入String类s返回s的长度,即将String类输入,映射到一个Integer类输出,并且在map方法里把它添加到一个Integer类的List来执行这个apply行为。

方法引用

方法引用让你可以重复使用现有的方法定义,并像lambda一样传递它们。实际上某些情况下虽然方法引用简洁,但是不如lambda表达式可读性高。先举一个例子,我们将前面第一个lambda表达式变为方法引用就是

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abcd","abc"};
Arrays.sort(s1, Comparator.comparingInt(String::length));
System.out.println(Arrays.toString(s1));
}

}

java Comparator.comparingInt(String::length) 这里就是方法引用,其中的方法不能带括号
方法引用可以被看做仅仅调用特定方法的lambda的一种快捷写法,如果一个lambda代表的只是数值解调用这个方法,那么最好是用名称去调用它,而不是描述如何调用它。
方法引用主要有三类:
1.指向静态方法的方法引用,例如Integer的parseInt方法,写作Integer::ParseInt
2.指向任意类型实例方法的方法引用,例如String的length方法,写作String::length
3.指向现有对象的实例方法的方法引用,假设你有一个局部变量a,用于存放A类型的对象,它支持实例方法m,那么你就可以写a::m。
第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是lambda的一个参数,比如lambda表达式(String s) -> s.toUpperCase()可以写作String::toUpperCase,但第三种方法引用指的是,你在lambda中调用一个已经存在的外部对象中的方法,例如lambda表达式() -> expensiveTransaction.getValue()可以写作expensiveTransaction::getValue这里的expensiveTransaction是一个外部对象,不是lambda的一个参数。
现在,我们的Function可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.xzc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;


public class Test {
public static <T,R> List<R> map(List<T> list, Function<T,R> f){
List<R> result = new ArrayList<>();
for(T l : list){
result.add(f.apply(l));
}
return result;
}
public static void main(String[] args) {
List<Integer> l = map(Arrays.asList("lambda","in","actions"), String::length);//方法引用 之前是(String s)->s.length()
System.out.println(l);
}
}

复合Lambda表达式

许多函数式接口,比如用于传递lambda表达式的comparator,function,predicate都提供了允许你进行复合的方法,意味着你可以把多个简单的lambda表达式复合成复杂的表达式,函数式接口为什么有多个方法呢?这是因为使用的都是默认方法

比较器复合

java Arrays.sort(s1, Comparator.comparingInt(String::length)) 展示了静态方法comparingInt根据提取用于比较的键值的Function来返回一个comparator。

逆序

如果我想对字符串长度逆序怎么办,接口有一个默认方法reversed可以使给定的比较器逆序。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abcd","abc"};
Arrays.sort(s1, Comparator.comparingInt(String::length).reversed());
System.out.println(Arrays.toString(s1));
}

}

输出结果

[abcd, abc, ab, a]

比较器链

如果有两个字符串长度一样,哪个排在前面呢?可以来第二个比较,有一个默认方法是thenComparing。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abd","abc"};
Arrays.sort(s1, Comparator.comparingInt(String::length).reversed());
System.out.println(Arrays.toString(s1));
}

}

这里我们输出结果是

[abd, abc, ab, a]

如果我们想先按长度逆序排序,再按字典顺序排序呢,也就是abc要在abd前面该怎么办,直接用thenComparing方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
package com.xzc;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
public static void main(String[] args) {
String[] s1 ={"ab","a","abd","abc"};
Arrays.sort(s1, Comparator.comparingInt(String::length).reversed().thenComparing(String::toString));
System.out.println(Arrays.toString(s1));
}
}

输出结果

[abc, abd, ab, a]

谓词复合

谓词接口包括三个方法:negate,and和or,让你可以重用已有的predicate来创建更复杂的谓词。
我们先新建一个Apple类:

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
package com.xzc;

public class Apple {
private int weight;
private String color;
public Apple(){

}
public Apple(int weight,String color){
this.weight = weight;
this.color = color;
}
public void setWeight(int weight) {
this.weight = weight;
}

public int getWeight() {
return weight;
}
public void setColor(String color){
this.color = color;
}
public String getColor(){
return this.color;
}

@Override
public String toString() {
return "Apple{" +
"weight=" + weight +
", color='" + color + '\'' +
'}';
}
}

我们要把Apple类的List中的红苹果提取出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.xzc;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;

public class Test {
public static <T> List<T> getRedApple(List<T> list, Predicate<T> p){
List<T> results = new ArrayList<>();
for (T l : list){
if (p.test(l)){
results.add(l);
}
}
return results;
}
public static void main(String[] args) {
System.out.println(getRedApple(Arrays.asList(new Apple(150,"red"),new Apple(160,"green"),new Apple(160,"red")),(Apple a) -> (a.getColor().equals("red")||a.getColor().equals("Red"))));
}


}

输出结果:

[Apple{weight=150, color=’red’}, Apple{weight=160, color=’red’}]

可以看到我们这里的predicate的test方法传入了我们的lambda表达式,判断苹果是红的。
如果我们想进一步,要重量大于150呢?用and方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.xzc;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;

public class Test {
public static <T> List<T> getRedApple(List<T> list, Predicate<T> p){
List<T> results = new ArrayList<>();
for (T l : list){
if (p.test(l)){
results.add(l);
}
}
return results;
}
public static void main(String[] args) {
Predicate<Apple> p = (Apple a) -> (a.getColor().equals("red")||a.getColor().equals("Red"));
System.out.println(getRedApple(Arrays.asList(new Apple(150,"red"),new Apple(160,"green"),new Apple(160,"red")),p.and(a -> a.getWeight() > 150)));
}


}

输出结果

[Apple{weight=160, color=’red’}]

这里我们把p先单独拿出来,方便观察,注意

java p.and(a -> a.getWeight() > 150)
没有声明a是Apple类型,这里lambda表达式进行了类型检查。我们对p进行了and复合,要求重量在150之上,如果我们要不是红苹果的呢?用negate复合即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.xzc;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;

public class Test {
public static <T> List<T> getRedApple(List<T> list, Predicate<T> p){
List<T> results = new ArrayList<>();
for (T l : list){
if (p.test(l)){
results.add(l);
}
}
return results;
}
public static void main(String[] args) {
Predicate<Apple> p = (Apple a) -> (a.getColor().equals("red")||a.getColor().equals("Red"));
System.out.println(getRedApple(Arrays.asList(new Apple(150,"red"),new Apple(160,"green"),new Apple(160,"red")),p.negate()));
}


}

输出结果:

[Apple{weight=160, color=’green’}]

可以看到筛出了不是红苹果的。

函数复合

最后,还可以把Function接口所代表的lambda表达式复合起来。接口有andThen和compose两个默认方法,都会返回一个Function实例。
andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。
但是注意f.andThen(g)代表着g(f(x)),即先执行f,再执行g,也符合andThen语义,f.compose(g)则是代表着f(g(x)),即f里复合g,也符合compose的语义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.xzc;


import java.util.function.Function;

public class Test {

public static void main(String[] args) {
Function<Integer,Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> 2 * x;
Function<Integer,Integer> h = f.andThen(g);
Function<Integer,Integer> y = f.compose(g);
System.out.println(h.apply(1));
System.out.println(y.apply(1));
}


}

输出结果

4
3

这里h就是先把1加1,得到2,再乘2,得到4.而y呢是先执行g,先把1乘2得到2,再执行f,加1得到3。数学函数如此,可以类比把function泛型的各种类型输入得到的输出再做映射,比如我们得到了字符串长度,再+1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.xzc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;


public class Test {
public static <T,R> List<R> map(List<T> list, Function<T,R> f){
List<R> result = new ArrayList<>();
for(T l : list){
result.add(f.apply(l));
}
return result;
}
public static void main(String[] args) {
Function<String,Integer> f = String::length;
List<Integer> l = map(Arrays.asList("lambda","in","actions"), f.andThen(x -> x+1));
System.out.println(l);
}
}

输出结果

[7, 3, 8]

之前没有复合的结果是

[6, 2, 7]

可以发现,进行函数复合可以加快编程效率,就是注意复合是复合lambda表达式,不要传入错的参数。


Java·Lambda表达式
http://example.com/2021/05/04/Java%C2%B7Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F%C2%B7%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
作者
AHawkeye
发布于
2021年5月4日
许可协议