什么是lambda表达式
可传递匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型、可能还有一个异常列表
匿名——他没有明确的名称
函数——他不像方法那样属于某个特定的类
传递——可以作为参数传递给方法或存储在变量中
简洁——无需像匿名类一样写很多模板代码
Lambda函数组成.png
函数式接口
public interface Predicate {
boolean test(Apple apple);
}
只定义了一个抽象方法的接口
函数式接口的作用
lambda表达式可以让你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实力
函数描述符
() -> void 描述lambda表达式的类型
@FunctionalInterface表示接口会设置成函数式接口
lambda环绕执行模式
资源处理时总是要打开资源,然后做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那这些重要代码,这就是所谓的环绕执行模式
文件中读取一行
public static String processFile() throws IOException{
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))){
return br.readLine();
}
}
这段代码只能读取文件的第一行,如果你想返回头两行,甚至是返回使用最频繁的词,该怎么办?在理想的情况下,你要重用执行设置和清理代码,并告诉
processFile方法对文件执行不同的操作。这样看就很眼熟,你需要吧processFile的行为参数话。你需要一种吧行为传递给processFile,以便他可以利用BufferReader执行不同的行为
改造这个函数使行为参数化
1,构造一个(BufferedReader br) -> String的函数描述
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader br) throws IOException;
}
2,改造函数
public static String processFile(BufferedReaderProcessor processor) throws IOException{
try (BufferedReader br =
new BufferedReader(new FileReader("data.txt"))){
return processor.process(br);
}
}
3,将lambda表达式传入函数中
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
实现了读取两行
使用函数式接口
函数式接口的抽象方法签名成为函数描述符
Java8库啥急事帮我们在java.util.function包中引入了几个新的函数式接口
Prediate函描述符 T -> boolean
Consumer函数描述符 T -> void
Function函数描述符 T -> R
Supplier函数描述符 () -> T
UnaryOperator函数描述符 T -> T
BinaryOperator函数描述符 (T,T) -> T
BiPredicate函数描述符 (L,R) -> boolean
BiConsumer函数描述符(T,U) -> void
BiFunction函数描述符(T,U) -> R
原始类型特化
为了减少拆箱,java8还提供了不是引用类型的函数式接口,还有很多
IntPredicate int -> boolean
异常
任何函数式接口都不可以抛出受检异常,如果你需要lambda表达式抛出异常,有两种办法
1,顶一个自己的函数式接口
2,把lambda表达式包在一个try/catch中
Lambda类型检查
lambda类型是从上下文推断出来的
同样的Lambda不同的函数式接口
是要抽象方法签名相同就可以使用同一个lambda
菱形运算
利用泛型推断出类型
特殊的void兼容规则
如果一个lambda主体是一个语句表达式,它就和一个返回void的函数描述符兼容
类型推断
Java编译器会从上下文推断出用什么函数接口来配合lambda表达式,可以推断出适合的签名
processFile(br -> br.readLine() + br.readLine());
使用局部变量
lambda可以使用自由变量(不是参数,而是外层作用域中定义的变量),就像匿名类一样,被称为捕获lambda
int i = 1000;
Runnable runnable = () -> System.out.println(i);
对变量的限制。lambda可以引用实例变量和静态变量。但局部变量必须显示声明为final,或事实上是final。换句话说,lambda表达式只能捕获指派给他的局部变量一次。(捕获实例变量可以被看做捕获最终变量this)。下面这段代码就是错误的
int i = 1000;
Runnable runnable = () -> System.out.println(i);
i= 20000;//lambda表达式引用局部变量需要声明为final或者事实是final的,所以不能修改
为什么对局部变量限制
1,实例变量和局部变量背后的实现有一个关键不同。实例变量存储在对中,局部变量存储在栈中,如果lambda可以直接访问局变量,而且lambda是在一个线程中使用,则使用lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量,因此,java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量,如果局部变量仅仅赋值一次就没有什么区别了
2,这一限制不鼓励你使用改变外部变量的典型命令编程模式(阻碍很容易做到的并且处理)
闭包
闭包就是一个函数的实例,且他可以无限制的访问那个函数的非本地变量
方法引用
让你可以重复使用现有的方法定义,并像lambda一样传递他们
lists.sort(Comparator.comparing(Apple::getWeight));
如何构建方法引用
1,指向静态方法的方法引用
2,指向任意类的实例方法的方法引用
3,指向现有对象实例方法的方法引用
构造函数引用
Classname::new
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
等价于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
复合lambda表达式的有用方法
比较器复合Comparator
1,逆序
lists.sort(Comparator.comparing(Apple::getWeight).reversed());
2,比较链
lists.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
谓词复合Predicate
negate 非
or 或
and 并且
函数复合Function
andThen
一个函数是f(x)一个函数是g(x),andThen后执行 g(f(x))
compose
一个函数是f(x)一个函数是g(x),andThen后执行 f(g(x))
网友评论