第一个Lambda表达式
先来看一段根据字符串长度排序的Test.java
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表达式取而代之:
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的布尔表达式时,就可以使用这个接口。直接上代码。
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的对象,并对它执行某些操作,就可以用这个借口。直接上代码。
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,将输入对象的信息映射到输出,可以使用这个接口,直接上代码。
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表达式变为方法引用就是
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可以这么写:
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可以使给定的比较器逆序。
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。
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方法即可。
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类:
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中的红苹果提取出来:
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方法即可。
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复合即可。
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的语义。
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。
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表达式,不要传入错的参数。