可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式。
Lambda表达式包括三个部分:参数列表,箭头,Lambda主体。例如:(String s) -> s.length();

在哪里使用Lambda表达式

函数式接口:只定义了一个抽象方法(可以包括若干默认方法)的接口是函数式接口。
Lambda表达式允许直接以内联的方式为函数式接口的抽象方法提供实现,并把整个表示打作为函数式接口的实例(该实例即简化后的匿名函数的实例)

  1. Runnalbe r1 = () -> System.out.println("这是Lbamda表示式");
  2. Runnable r2 = new Runnable() {
  3. public void run() {
  4. System.out.println("这是匿名函数");
  5. }
  6. }

函数描述符和常用的函数式接口

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。这种抽象方法就叫做函数描述符。使用一种特殊的表示法来描述Lambda和函数式接口的签名。例如() -> void代表参数列表为空,且返回void的函数,即Runnable接口所代表的签名。

函数式接口 函数描述符
Predicate 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

类型检查

如何确定Lambda表达式的类型是从使用的上下文中确定的。
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);为例

1查看使用的上下文,filter函数定义,确定目标类型是Predicate
List<Apple> filter(List<Apple> inventory, Predicate<Apple> p)
2确定接口的抽象方法,Predicate的抽象接口boolean test(Apple apple)
3确定函数描述符Apple ->boolean
4比较函数描述符和Lambda表达式的签名。发现相匹配,类型检查正确

如果lambda表达式抛出异常,则抽象方法所声明的throws语句也必须与之匹配。

  1. public interface ThrowLambda {
  2. void test(String s) throws InvalidParameterException;
  3. }
  4. ThrowLambda lambda = (s) -> {
  5. if (s == null) {
  6. throw new InvalidParameterException();
  7. }
  8. };

我尝试了代码,感觉这个限制不是必须的

  1. public interface ThrowLambda {
  2. void test(String s);
  3. }
  4. ThrowLambda lambda = (s) -> {
  5. if (s == null) {
  6. throw new InvalidParameterException();
  7. }
  8. System.out.println("有效入参");
  9. };
  10. try {
  11. lambda.test(null);
  12. } catch (InvalidParameterException e) {
  13. System.out.println("无效入参");
  14. }
  15. // 正常打印了 无效入参

针对类型检查的原理,只要能将函数描述符和Lambda表达式的签名匹配上,同一个Lambda表达式可以匹配不同的函数式接口。

  1. public interface Supplier<T> {
  2. T get();
  3. }
  4. public interface Temperature<Integer> {
  5. Integer getTemperature();
  6. }
  7. Supplier<Integer> supplier = () -> 42;
  8. Temperature<Integer> temperature = () -> 42;
  9. Cpmparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareT0(a2.getWeight());
  10. ToIntBiFunction<Apple, Apple> c2 =
  11. (Apple a1, Apple a2) -> a1.getWeight().compareT0(a2.getWeight());
  12. BiFunction<Apple, Apple, Integer> c3 =
  13. (Apple a1, Apple a2) -> a1.getWeight().compareT0(a2.getWeight());

方法引用

方法引用可以看错是一种调用特定的Lambda表达式的一种快捷写法。方法引用就是根据已有的方法实现来创建Lambda表达式。但是显式地指明方法的名称,代码的可读性会更好。
使用方法就是将目标引用放在分隔符 :: 前,方法的名称放在分隔符后。例如Apple::getWeight就是引用了Apple类中定义的getWeight方法

三种方法引用方式

1指向静态方法的方法引用。例如Integer::parseInt
2指向任意类型实例方法的方法引用。例如:String::getLength
3指向现有对象的实例方法的方法引用。有一个局部变量salary,属于Salary类,有一个add的方法,则可以这么调用salary::add

那么2和3有什么区别呢?
当对象本身就是Lambda表达式的一个参数时,我们使用第二种方式。例如:

Function<String, Integer> f1 = String::length;
Function<String, Integer> f2 = (string) -> string.length();

当只是要在Lambda表示式中调用一个已经存在的外部对象的方法时,使用第三方重视。例如:

public class Salary {
    private int a = 0;
    public int add(int number) {
        a = a + number;
        return a;
    }
}

Salary salary = new Salary();
Function<Integer, Integer> f1 = salary::add;
Function<Integer, Integer> f2 = (addNumber) -> salary.add(addNumber);

闭包和Lambda表达式局部变量限制

闭包就是一个函数的实例,且它可以无限制的访问函数的非本地变量。

Lambda和匿名类可以做到类似的事情:可以作为参数传递给方法,且可以访问其作用域之外的变量。但是java对其做出了限制,只能捕获指派给它们的局部变量一次(而实例变量和静态变量则没有限制)。这就意味着在Lambda和匿名类中不能修改外部传入的局部变量。

为什么要限制
因为局部变量是保存在栈上,它们应该仅限于它们本身所在的线程。而Lambda表达式可能被运行于其它线程。如果允许Lambda表达式修改这些局部变量,就会引发造成线程不安全的新的可能性,更加难以维护。而堆是线程之间共享的,所以实例变量和静态变量可以被修改(注意,这里实例变量可以被修改是指的实例变量对象,实例变量的引用也是局部变量,不能被修改,即不能让实例变量的引用指向另外一个实例变量)。