《Java 8实战》- Lambda 详解

目录

1、Lambda 表达式:参数 -> 主体

2、在函数式接口上使用 Lambda 表达式

3、实例:创建环绕行为

4、描述常见函数描述符的函数式接口

5、利用 Lambda 表达式抛出异常的方法

6、编译器对 Lambda 做类型检查、类型推断

7、和 void 兼容的 Lambda

8、Lambda 内部引用局部变量

9、方法引用

10、构造函数引用

11、复合 Lambda 表达式

12、实践


1、Lambda 表达式参数 -> 主体

  • 一些 Lambda 表达式:
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);
    }

 

2、在函数式接口上使用 Lambda 表达式

整个 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

 

3、实例:创建环绕行为

  • 背景:processFile 打开一个资源,进行一些处理,关闭资源
public static String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
  	return br.readLine();     // 此处的行为只能读资源第一行,需要进行 “行为参数化”
  }
}
  • 步骤1:根据需要被参数化的行为确定 Lambda 表达式的签名  (BufferedReader)-> String
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());    // 参数化的资源处理行为:接收资源,返回处理结果
  • 步骤2:使用函数式接口传递行为(根据 Lambda 签名推出所需要的接口方法的签名,从而决定选择现成接口或自定义接口)
@FunctionalInterface
public interface BufferedReaderProcessor {               // 自定义函数式接口
	String process(BufferedReader b) throws IOException;   // 和 Lambda 签名相同的抽象方法 process:(BufferedReader)-> String
}
  • 步骤3:将函数式接口作为参数以传递行为给新方法,在新方法中执行被参数化的行为
public static String processFile(BufferedReaderProcessor p) throws IOException {  // 将自定义函数式接口 BufferedReaderProcessor 作为 processFile 的参数以传递被参数化的行为
  try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    return p.process(br);     // 执行被参数化的行为,即执行函数式接口的抽象方法
  }
}
  • 步骤4:通过传递不同的 Lambda 表达式让 processFile 方法执行不同的资源处理行为
String oneLine = processFile((BufferedReader br) -> br.readLine());                      // 读一行
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());     // 读两行

 

4、描述常见函数描述符的函数式接口

public interface Predicate<T>{
	boolean test(T t);            // Predicate: T -> boolean
}
public interface Consumer<T>{ 
	void accept(T t);             // Consumer: T -> void
}
public interface Function<T, R>{
	R apply(T t);                 // Function: T -> R
}
// 这些常见的函数式接口还有为避免装箱而定义的原始类型特化版本,如 IntConsumer,给此接口的抽象方法 accept 传 int 参数时就不会自动装箱了

 

5、利用 Lambda 表达式抛出异常的方法

注:任何函数式接口都不允许抛出受检异常(checked exception)

  • 方法1:自定义函数式接口,声明受检异常
@FunctionalInterface
public interface BufferedReaderProcessor {
	String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();
  • 方法2:把 Lambda 包在 try/catch 中,显式捕捉受检异常
Function<BufferedReader, String> f = (BufferedReader b) -> {
  try {
  	return b.readLine();
  }
  catch(IOException e) {
  	throw new RuntimeException(e);
  }
};

 

6、编译器对 Lambda 做类型检查、类型推断

目标类型:Lambda的类型是从使用Lambda的上下文推断出来的,上下文中Lambda表达式需要的类型称为目标类型(上下文:接受 Lambda 传递的方法的参数,或接受 Lambda 的值的局部变量)

PS:只要 Lambda 的签名和抽象方法的签名匹配,则同一个 Lambda 表达式可以赋给不同的函数式接口

类型检查:Lambda 可以为函数式接口生成实例,但表达式本身没有包含它实现的函数式接口的信息,因此需要检查 Lambda 表达式的签名与函数式接口的抽象方法的签名是否匹配(入参、返回)

类型推断:编译器会从目标类型推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因此可以在 Lambda 中省去标注参数类型。

static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);                 // 上下文:接受 Lambda 传递的方法 filter 的参数为函数式接口 Predicate,其抽象方法 T -> boolean
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));   // 推断 Lambda 的类型为 Apple -> boolean,故 a 类型为 Apple(仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略)
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());       // (T,T) -> int,故 a1,a2 类型为 Apple

 

7、和 void 兼容的 Lambda

如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)

Predicate<String> p = s -> list.add(s);  // Predicate返回了一个boolean
Consumer<String> b = s -> list.add(s);   // Consumer返回了一个void(尽管List的add方法返回boolean,但是由于lambda主体是表达式,因此也与返回void的Consumer的accept方法兼容)

 

8、Lambda 内部引用局部变量

捕获 Lambda:引用定义在外层作用域的变量的 Lambda

Lambda可以没有限 制地捕获实例变量和静态变量,但局部变量必须显式声明为final, 或事实上是final

原因:实例变量存储在中,局部变量存储在上,若 Lambda 的线程和分配该变量的线程不同,则可能出现这个变量被回收后去访问它的情况,因此 Lambda 访问的实际上是局部变量的副本,当此局部变量是final时,副本就等价于原始变量了

一个Java进程对应唯一一个JVM实例,一个JVM实例唯一对应一个堆;一个java进程可以包含多个线程,每一个线程有一个自己私有的栈 —— 一个进程的多个线程共享堆不共享栈

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);    // Lambda捕获了portNumber变量

 

9、方法引用

个人总结:从 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 是某现有对象)

 

10、构造函数引用

构造函数引用:ClassName::new (注意根据构造函数参数列表选择抽象方法匹配的函数式接口)     

构造函数签名              Lambda                                                       方法引用 
Apple()                 Supplier<Apple> c1 = () -> new Apple()                       Supplier<Apple> c1 = Apple::new       // Supplier 的抽象方法 get:()->T;
                        Apple a1 = c1.get();                                         Apple a1 = c1.get();

Apple(Integer weight)   Function<Integer, Apple> c2=(weight)->new Apple(weight);     Function<Integer, Apple> c2 = Apple::new;   // Function 的抽象方法 apply:T -> R
                        Apple a2 = c2.apply(110);                                    Apple a2 = c2.apply(110);

 

11、复合 Lambda 表达式

个人总结:复合其实就是使用函数式接口中 接受或返回函数式接口类型变量的方法

  • 将Lambda赋给函数式接口中接受此接口类型的变量

  • 通过 Lambda表达式提供的函数式接口实例调用此接口中返回此接口类型变量的方法

Comparator  比较器复合:

list.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));    // 比较器复合( list 是 List 类型变量)
  void sort(Comparator<? super E> c)
  Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)       // Apple::getWeight 给接口 Function 提供实例
  Comparator<T> reversed()
  Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor)   // Apple::getCountry 给接口 Function 提供实例

Predicate   谓词复合:

Predicate<Apple> greenApple = a -> "green".equals(a.getColor())
Predicate<Apple> redAndHeavyApple = greenApple.negate().and(a -> a.getWeight() > 150);  // 谓词复合 (不能直接 Lambda.and())
	Predicate<T> negate()                         // a -> "green".equals(a.getColor()) 给接口 Predicate 提供实例,然后调用接口的 negate 方法
  Predicate<T> and(Predicate<? super T> other)  // negate 方法返回 Predicate 类型变量,然后调用此接口的 and 方法,返回 Predicate 类型变量赋给 redAndHeavyApple

 

12、实践

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, U, V> T getObject(U u, V v, GoodsFunction<T, U, V> function) {   // 提示 public 可以换成 private
        return function.apply(u, v);
    }

    public static <T> List<T> selectObjects(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t)) {
                result.add(t);
            }
        }
        return result;
    }

    public static <T extends Goods> void printSortedObjects(List<T> 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 <T extends Goods> void printShortNameAndCheapObjects(int maxLength, List<T> list) {
        Predicate<T> shortName = g -> g.getName().length() < maxLength;
        List<T> 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> goods = new ArrayList<>();

        GoodsFunction<Goods, String, Double> f1 = (name, price) -> new Goods(name, price);  // 提示这里可以换成 方法引用
        Goods goods1 = f1.apply("book", 100D);
        goods.add(goods1);

        GoodsFunction<Goods, String, Double> 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, U, V> {
    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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值