可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感觉也更自然。
先前:
List<Apple> lists= Arrays.asList(new Apple("red", 50),
new Apple("green", 40),
new Apple("blank", 90)
);
lists.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight()));
之后(使用方法引用和java.util.Comparator.comparing):
lists.sort(comparing(Apple::getWeight));
你为什么应该关心方法引用?方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。
它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。它是如何工作的呢?
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法。
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
方法引用主要有三类。
指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。
指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda
表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new。它的功能与指向静态方法的引用类似。例如,假设有一个构造函数没有参数。
它适合Supplier的签名() -> Apple。你可以这样做:
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
这就等价于:
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
如果你的构造函数的签名是Apple(Integer weight),那么它就适合Function接口的签
名,于是你可以这样写:
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、FunctionPredicate都提供了允许你进行复合的方法。
这是什么意思呢?在实践中,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。你可能会想,函数式接口中怎么可能有更多的方法呢?(毕竟,这违背了函数式接口的定义啊!)窍门在于,我们即将介绍的方法都是默认方法,也就是说它们不是抽象方法。
我们前面看到,你可以使用静态方法Comparator.comparing,根据提取用于比较的键值
的Function来返回一个Comparator,如下所示:
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
inventory.sort(comparing(Apple::getWeight).reversed());
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry));//两个苹果一样重时,进一步按国家排序
谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复杂的谓词。
比如,你可以使用negate方法来返回一个Predicate的非,比如苹果不是红的:
//产生现有Predicate对象redApple的非
Predicate<Apple> notRedApple = redApple.negate();
最后,你还可以把Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。
比如,假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组
合成一个函数h,先给数字加1,再给结果乘2:
//数学上会写作g(f(x))或(g o f)(x)
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);//结果为4
你也可以类似地使用compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)), 而andThen则意味着g(f(x)):
//数学上会写作f(g(x))或(f o g)(x)
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1);//结果为3
示例:
public class Letter{
public static String addHeader(String text){
return "From Raoul, Mario and Alan: " + text;
}
public static String addFooter(String text){
return text + " Kind regards";
}
public static String checkSpelling(String text){
return text.replaceAll("labda", "lambda");
}
}
现在你可以通过复合这些工具方法来创建各种转型流水线了,比如创建一个流水线:先加上抬头,然后进行拼写检查,最后加上一个落款
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling).andThen(Letter::addFooter);