Lambda表达式
1.1 Lambda 管中窥豹
可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式:它没有名称,但是有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
在Java8之前,传递代码十分繁琐,现在,利用Lambda表达式,你可以来体验行为参数化了!
之前的代码:
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
};
用Lambda:
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Java8中有效的Lambda表达式:
Lambda的基本语法是
(参数) ->表达式
或者
(参数)->{语句;}
1.2 在哪里可以使用以及如何使用
可以在函数式接口上使用Lambda表达式
1.2.1 函数式接口
函数式接口就是只定义一个抽象方法的接口
例如 Comparator 和Runnable
java.util.Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
}
java.lang.Runnable
public interface Runnable{
void run();
}
用函数式接口可以干什么呢?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
1.2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。
现在,只要知道Lambda表达式可以被赋给一个 变量,或传递给一个接受函数式接口作为参数的方法就好了,当然这个Lambda表达式的签名要 和函数式接口的抽象方法一样。比如,在我们之前的例子里,你可以像下面这样直接把一个 Lambda传给process方法:
public void process(Runnable r){
r.run();
}
process(() -> System.out.println("This is awesome!!"));
此代码执行时将打印“This is awesome!!”。
Lambda表达式()-> System.out.println ("This is awesome!!")
不接受参数且返回void。
这恰恰是Runnable接口中run方法的签名。
@FunctionalInterface是怎么回事
这个标注用于表示该接口会设计成一个函数式接口。
1.3 把Lambda付诸实践:环绕执行模式
public static String processFile() throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}
1.3.1 第一步 :记得行为参数化
这段代码是有局限性的。改变操作会对文件执行不同的操作。现在需要一种方法把行为传递给processFile,以便它可以利用BufferedReader 执行不同的行为。
String result = processFile((BufferedReader br) ->
br.readLine() + br.readLine());
1.3.2 第二步:使用函数式接口来传递行为
Lambda仅可以用于上下文是函数式接口的情况,所以需要用@FunctionalInterface注解创建自己的函数式接口
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferReader b) throws IOException;
}
//现在就可以把这个接口座位新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor p) throws IOException{
...
}
1.3.3:第三步:执行一个行为
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
1.3.4 第四步:传递lambda
现在你就可以通过传递不用的Lambda重用processFile方法,并以不同的方式处理文件了。
String oneLine = processFile((BUfferedReader br) -> br.readLine());
String twoLines = processFile((BUfferedReader br) -> br.readLine()+br.readLine());
上面四个步骤的总结:
public static String processFile() throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferReader b) throws IOException;
}
//现在就可以把这个接口座位新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor p) throws IOException{
...
}
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
String oneLine = processFile((BUfferedReader br) -> br.readLine());
String twoLines = processFile((BUfferedReader br) -> br.readLine()+br.readLine());
1.4 使用函数式接口
函数式接口定义且只定义了一个抽象方法。为了应用不同的Lambda表达式,你需要一套能够描述常见函数描述符的函数式接口。下面会介绍Predicate、Consumer 和Function,更完整的见下表。
1.4.1 Predicate
java.util.function.Predicate 接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean。在你需要 表示一个涉及类型T的布尔表达式时,就可以使用这个接口。
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
1.4.2 Consumer
java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)。 你如果需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public static <T> void forEach(List<T> list,Consumer<T> c){
for(T i:list){
c.accept(i);
}
}
forEach(
Arrays.asList(1,2,3,4,5),
(Integer i) -> System.out.println(i)
);
1.4.3 Function
java.util.function.Function接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
public static <T, R> List<R> map(List<T> list,
Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s: list){
result.add(f.apply(s));
}
return result;
}
// [7, 2, 6]
List<Integer> l = map(
Arrays.asList("lambdas","in","action"),
(String s) -> s.length()
);
原始类型特化:
一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如:DoublePredicate、IntConsumer、IntFuncition等。另外还有针对输出参数类型的变种:ToIntFunciton,IntToDoubleFunciton等。
1.5 类型检查、推断、限制
它们可以从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型。
1.6方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下, 比起使用Lambda表达式,它们似乎更易读,感觉也更自然。
//先前
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight()));
//之后
inventory.sort(comparing(Apple::getWeight));
1.6.1 管中窥豹
当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如, Apple::getWeight 就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为 你没有实际调用这个方法。
如何构建方法引用:
主要有三类:
(1)指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::paresint)。
(2)指向任意类型实例方法的方法引用(例如 String 的 length 方法,写作 String::length)。
(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction 用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensive- Transaction::getValue)。
1.6.2 构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用: ClassName::new。
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);
1.7 Lambda和方法引用实战
1.7.1 第一步 传递代码
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
1.7.2 第二步 使用匿名类
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
1.7.3 第三步 使用Lambda
inventory.sort((a1,a2)
-> a1.getWeight().compareTo(a2.getWeight())
);
Comparator具有一个叫作comparing的静态辅助方法, 它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
现在可以这样写 :
import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));
1.7.4 第四步 使用方法引用
前面解释过,方法引用就是替代那些转发参数的Lambda表达式的语法糖。你可以用方法引用让你的代码更简洁。
inventory.sort(comparing(Apple::getWeight));
参考资料:《java8 In Action》