本章内容
- Lambda管中窥豹
- 在哪里以及如何使用Lambda
- 环绕执行模式
- 函数式接口,类型推断
- 方法引用
- Lambda复合
Lambda管中窥豹
可以把Lambda表达式理解为更简洁地表示可传递的匿名方法的一种方式,它没有名称,但是有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。下面我们来解释一下什么是匿名、函数、传递、简洁
匿名不像我们写的普通方法都有个明确的名称,写得少而想得多
函数我们说它是函数,是因为Lamdba函数不像方法那样属于特定的类。但是和方法一样,Lamdba有参数列表、函数主体、返回类型、还可能抛出异常
传递 Lambda表达式可以作为参数传递给方法或储存在变量中
简洁无需要匿名类那样写很多模板代码
由图3-1有三部分
(1)**参数列表:**这里它采用了Comparator中的compare方法的参数,两个Apple
(2)**箭头:**箭头把参数列表与Lamdba主体分割开。
(3)**Lamdba主体:**比较两个Apple的重量,表达式就是lamdba的返回值了
在哪里以及如何使用Lambda
函数式接口
在函数式接口上使用lamdba表达式,上述代码中,我们将lamdba表达式当做filter的第二个参数,因为它这里需要Predicate,而它恰恰是一个函数式接口,下面我们来解释什么是函数式接口。
从上一章中我们为fliter方法的行为参数化使用的是Predicate,他就是一个函数式接口,因为Predicate是一个接口并且只定义一个抽象方法,JavaApi中一些其他的函数式接口如下
Lamdba表达式允许你直接以内联的方式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体来说,是函数式接口一个具体实现的实例)。解决了以往匿名类带来的笨拙
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描描述。例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。
环绕执行模式
第一步:记得行为参数化
String result = processFile((BufferedReader br) ->
br.readLine() + br.readLine());
第二步:使用函数式接口来传递行为
第三步:执行一个行为
第四步:传递Lamdba
完整步骤
package com.learn.java8.second;
import java.io.BufferedReader;
import java.io.FileReader;
/**
* 函数式接口
*/
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader bufferedReader) throws Exception;
}
class Test{
// 现在你就可以把这个接口作为新的processFile方法的参数了:
public static String processFile(BufferedReaderProcessor bufferedReaderProcessor){
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\learn\\java8-master\\java8\\src\\main\\resources\\data.txt"));
return bufferedReaderProcessor.process(bufferedReader);
}catch (Exception ex){
System.out.println("异常");
}
return null;
}
public static void main(String[] args) {
// 传入Lamdba表达式
String onLine = processFile((BufferedReader br)-> br.readLine());
String twoLine = processFile((BufferedReader br)-> br.readLine() + br.readLine());
System.out.println(onLine);
System.out.println(twoLine);
}
}
使用函数式接口
Predicate
Consumer
public static <T> void forEach(List<T> list, Consumer<T> consumer){
for (T item:list){
consumer.accept(item);
}
}
forEach(Arrays.asList(1,3,4,5,6),(Integer i) -> System.out.println(i));
Function
/** 它接受一个泛型T的对象,并返回一个泛型R的对象。
* @param list
* @param consumer
* @param <T>
* @param <R>
*/
public static <T,R> List map(List<T> list, Function<T,R> consumer){
List<R> result = new ArrayList<>();
for (T item:list){
result.add(consumer.apply(item));
}
return result;
}
List<Integer> list = map(Arrays.asList("li","asasdasd","asdasdadasdasd"),(String s1) -> s1.length());
// [2, 8, 14]
类型检查、类型推断以及限制
类型检查
Lamdba的类型是从使用Lamdba的上下文推断出来的,上下文(接收它传递的方法的参数,接受它的值的局部变量)中Lamdba表达式需要的类型称为目标类型,下面通过一个例子看看lamdba表达式的背后发生了什么。
同样的lamdba,不同的函数式接口
package com.learn.java8.second;
public interface TestLadmdba {
void test(Integer integer);
public static void main(String[] args) {
TestLadmdba i = (Integer p) -> test();
}
public static boolean test(){
return false;
}
}
你已经见过如何利用目标类型来查一个Lambda是否可以用于某个特定的上下文。其实,它也可以用来做一些略有不同的事:推断Lambda参数的类型。
类型推断
你还可以进一步简化你的代码.Java编译器会从上下文(目标类型)推断出用什么函数式接 口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到,这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可 以在Lambda语法中省去标注参数类型.换句话说,Java编译器会像下面这样推断Lambda的参数类型:
注意,当lamdba仅有一个类型需要推断的参数时,参数两边的括号也可以省略
使用局部变量
你可能会问自己,为什么ࡌ部变量有这些限制。第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了
这个限制。
第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式(我们会在以后的各章中解释,这种模式会阻碍很容易做到的并行处理)
现在,我们来介绍你会在Java 8代码中看到的另一个功能:方法引用。可以把它们视为某些Lambda的快捷写法
方法引用
管中窥豹
如何构建方法引用
(1)指向静态方法的引用(如Integer的paraseInt方法),写成(Integer::paraseInt)
(2)指向任意类型实例方法的方法引用(例如String的length方法,写成String::length)
(3)指向现有对象的实例的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)
构造函数引用
Lamdba和方法引用实战
第一步:传递代码
第二步:使用匿名类
第三步:使用Lamdba表达式
第四步:使用方法引用
复合Lamdba表达式的有用方法
比较器复合
-
逆序
-
比较器链
谓语副词
Predicate<Apple> applePredicate = (Apple apple) -> apple.getColor().equals("red");
Predicate<Apple> notRedApple = applePredicate.negate();
Predicate<Apple> applePredicate = (Apple apple) -> apple.getColor().equals("red");
Predicate<Apple> redAndApple = applePredicate.and((Apple apple) -> apple.getWeight() > 150);
函数复合
package com.learn.java8.second;
import java.util.function.Function;
import java.util.function.ToIntFunction;
public class Demo {
public static void main(String[] args) {
Function<Integer,Integer> function1 = (Integer x) -> x + 1;
Function<Integer,Integer> function2 = (Integer x) -> x * 1;
// andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数((10+1) * 1),是一个先前再后的思想
Function<Integer,Integer> function3 = function1.andThen(function2);
Integer result = function3.apply(10);
System.out.println(result);
// compose同样返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数((10*1)) + 1,是一个先后再前的思想
Function<Integer,Integer> function4 = function1.compose(function2);
System.out.println(function4.apply(10));
}
}
文本转换
package com.learn.java8.second.demo;
import java.util.function.Function;
public class Letter {
public static String addHeader(String text){
return "Form zlx,zlx2 And Alan" + text;
}
public static String addFooter(String text){
return text + "Kind regards";
}
public static String checkSpelling(String text){
return text.replaceAll("lamdba","la");
}
public static void main(String[] args) {
Function<String,String> addHeader = Letter::addHeader;
Function<String,String> transformationPieline = addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
String result = transformationPieline.apply("lamdba ladlads adasdads");
System.out.println(result);
}
}
数学中的类似思想
积分
package com.learn.java8.second.demo;
import java.util.function.DoubleFunction;
public class Area {
public double integrate(DoubleFunction<Double> function,double a,double b){
return (function.apply(a) + function.apply(b)) * (b-a)/2.0;
}
public static void main(String[] args) {
double result = new Area().integrate((double x) -> x + 10,3,7);
System.out.println(result);
}
}
总结