目录
1、Lambda 表达式:参数 -> 主体
2、在函数式接口上使用 Lambda 表达式
3、实例:创建环绕行为
4、描述常见函数描述符的函数式接口
5、利用 Lambda 表达式抛出异常的方法
6、编译器对 Lambda 做类型检查、类型推断
7、和 void 兼容的 Lambda
8、Lambda 内部引用局部变量
9、方法引用
10、构造函数引用
11、复合 Lambda 表达式
12、实践
1、 (String s) -> s.length()
2、 (Apple a) -> a.getWeight() > 150
3、 () -> 42
4、 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
5、 (int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
整个 Lambda 表达式作为函数式接口的实例(Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法)
函数式接口:只定义了一个抽象方法的接口(不管有多少default方法)
@FunctionalInterface:给接口添加此标注表示该接口会设计成一个函数式接口,如果这个接口不是函数式接口编译器会报错
函数描述符:函数式接口的抽象方法的签名称为函数描述符
public interface Runnable{ // 函数式接口 Runnable
void run(); // 抽象方法 run:()-> void
}
Runnable r1 = () -> System.out.println("Hello World 1"); // Lambda表达式赋给一个变量
public void process(Runnable r){ // 接受函数式接口作为参数的方法 process
r.run();
}
process(() -> System.out.println("This is awesome!!")); // Lambda表达式传递给 process
public static String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine(); // 此处的行为只能读资源第一行,需要进行 “行为参数化”
}
}
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine()); // 参数化的资源处理行为:接收资源,返回处理结果
@FunctionalInterface
public interface BufferedReaderProcessor { // 自定义函数式接口
String process(BufferedReader b) throws IOException; // 和 Lambda 签名相同的抽象方法 process:(BufferedReader)-> String
}
public static String processFile(BufferedReaderProcessor p) throws IOException { // 将自定义函数式接口 BufferedReaderProcessor 作为 processFile 的参数以传递被参数化的行为
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()); // 读两行
public interface Predicate{
boolean test(T t); // Predicate: T -> boolean
}
public interface Consumer{
void accept(T t); // Consumer: T -> void
}
public interface Function{
R apply(T t); // Function: T -> R
}
// 这些常见的函数式接口还有为避免装箱而定义的原始类型特化版本,如 IntConsumer,给此接口的抽象方法 accept 传 int 参数时就不会自动装箱了
注:任何函数式接口都不允许抛出受检异常(checked exception)
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();
Function f = (BufferedReader b) -> {
try {
return b.readLine();
}
catch(IOException e) {
throw new RuntimeException(e);
}
};
目标类型:Lambda的类型是从使用Lambda的上下文推断出来的,上下文中Lambda表达式需要的类型称为目标类型(上下文:接受 Lambda 传递的方法的参数,或接受 Lambda 的值的局部变量)
PS:只要 Lambda 的签名和抽象方法的签名匹配,则同一个 Lambda 表达式可以赋给不同的函数式接口
类型检查:Lambda 可以为函数式接口生成实例,但表达式本身没有包含它实现的函数式接口的信息,因此需要检查 Lambda 表达式的签名与函数式接口的抽象方法的签名是否匹配(入参、返回)
类型推断:编译器会从目标类型推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因此可以在 Lambda 中省去标注参数类型。
static Collection filter(Collection c, Predicate p); // 上下文:接受 Lambda 传递的方法 filter 的参数为函数式接口 Predicate,其抽象方法 T -> boolean
List greenApples = filter(inventory, a -> "green".equals(a.getColor())); // 推断 Lambda 的类型为 Apple -> boolean,故 a 类型为 Apple(仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略)
Comparator c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); // (T,T) -> int,故 a1,a2 类型为 Apple
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)
Predicate p = s -> list.add(s); // Predicate返回了一个boolean
Consumer b = s -> list.add(s); // Consumer返回了一个void(尽管List的add方法返回boolean,但是由于lambda主体是表达式,因此也与返回void的Consumer的accept方法兼容)
捕获 Lambda:引用定义在外层作用域的变量的 Lambda
Lambda可以没有限 制地捕获实例变量和静态变量,但局部变量必须显式声明为final, 或事实上是final
原因:实例变量存储在堆中,局部变量存储在栈上,若 Lambda 的线程和分配该变量的线程不同,则可能出现这个变量被回收后去访问它的情况,因此 Lambda 访问的实际上是局部变量的副本,当此局部变量是final时,副本就等价于原始变量了
一个Java进程对应唯一一个JVM实例,一个JVM实例唯一对应一个堆;一个java进程可以包含多个线程,每一个线程有一个自己私有的栈 —— 一个进程的多个线程共享堆不共享栈
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber); // Lambda捕获了portNumber变量
个人总结:从 Lambda 主体确定所调用方法所属的类和方法名,再将 Lambda 整个表达式替换为 类::方法名 即可
方法引用适用场景:Lambda 表达式的主体直接调用一个方法 ( para->func(para) 或 para->para.func() )
(String a) -> Integer.parseInt(a,2) Integer::parseInt // 指向静态方法的方法引用
(str, i) -> str.substring(i) String::substring // 指向任意类型实例方法的方法引用
()->expensiveTransaction.getValue() expensiveTransaction::getValue // 指向现有对象的实例方法的方法引用(expensiveTransaction 是某现有对象)
构造函数引用:ClassName::new (注意根据构造函数参数列表选择抽象方法匹配的函数式接口)
构造函数签名 Lambda 方法引用
Apple() Supplier c1 = () -> new Apple() Supplier c1 = Apple::new // Supplier 的抽象方法 get:()->T;
Apple a1 = c1.get(); Apple a1 = c1.get();
Apple(Integer weight) Function c2=(weight)->new Apple(weight); Function c2 = Apple::new; // Function 的抽象方法 apply:T -> R
Apple a2 = c2.apply(110); Apple a2 = c2.apply(110);
个人总结:复合其实就是使用函数式接口中 接受或返回函数式接口类型变量的方法
将Lambda赋给函数式接口中接受此接口类型的变量
通过 Lambda表达式提供的函数式接口实例调用此接口中返回此接口类型变量的方法
Comparator 比较器复合:
list.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry)); // 比较器复合( list 是 List 类型变量)
void sort(Comparator super E> c)
Comparator comparing(Function super T, ? extends U> keyExtractor) // Apple::getWeight 给接口 Function 提供实例
Comparator reversed()
Comparator thenComparing(Function super T, ? extends U> keyExtractor) // Apple::getCountry 给接口 Function 提供实例
Predicate 谓词复合:
Predicate greenApple = a -> "green".equals(a.getColor())
Predicate redAndHeavyApple = greenApple.negate().and(a -> a.getWeight() > 150); // 谓词复合 (不能直接 Lambda.and())
Predicate negate() // a -> "green".equals(a.getColor()) 给接口 Predicate 提供实例,然后调用接口的 negate 方法
Predicate and(Predicate super T> other) // negate 方法返回 Predicate 类型变量,然后调用此接口的 and 方法,返回 Predicate 类型变量赋给 redAndHeavyApple
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import static java.util.Comparator.comparing;
public class LambdaTest {
public static T getObject(U u, V v, GoodsFunction function) { // 提示 public 可以换成 private
return function.apply(u, v);
}
public static List selectObjects(List list, Predicate predicate) {
List result = new ArrayList<>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
public static void printSortedObjects(List list) {
list.sort(comparing(T::getName, comparing(String::length)).thenComparing(T::getPrice));
for (T t : list) {
System.out.println("Sorted Goods:" + t.getName() + ":" + t.getPrice()); // SonarLint 提示用日志替换 System.out System.err
}
}
private static void printShortNameAndCheapObjects(int maxLength, List list) {
Predicate shortName = g -> g.getName().length() < maxLength;
List shortNameAndCheapObjects = selectObjects(list, shortName.and(g -> g.getPrice() < 100D));
for (Goods g : shortNameAndCheapObjects) {
System.out.println("ShortNameAndCheapGoods is " + g.getName() + ":" + g.getPrice());
}
}
public static void main(String[] args) {
List goods = new ArrayList<>();
GoodsFunction f1 = (name, price) -> new Goods(name, price); // 提示这里可以换成 方法引用
Goods goods1 = f1.apply("book", 100D);
goods.add(goods1);
GoodsFunction f2 = Goods::new;
Goods goods2 = f2.apply("pen", 10D);
goods.add(goods2);
Goods goods3 = getObject("paper", 5D, Goods::new);
goods.add(goods3);
printSortedObjects(goods);
int maxLength = 4;
printShortNameAndCheapObjects(maxLength, goods);
}
}
@FunctionalInterface
public interface GoodsFunction {
T apply(U u, V v);
}
class Goods {
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
Sorted Goods:pen:10.0
Sorted Goods:book:100.0
Sorted Goods:paper:5.0
ShortNameAndCheapGoods is pen:10.0