美文网首页
Lambda表达式

Lambda表达式

作者: PawsUp | 来源:发表于2019-01-30 20:53 被阅读0次

Lambda表达式

理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码。

基本语法

(parameters) -> expression(表达式)
(parameters) -> { statements; }(语句,注意语句的花括号)

  • 例如
  1. () -> {}
    这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
  2. () -> "Raoul"
    这个Lambda没有参数,并返回String作为表达式(Lambda没有return语句,因为已经隐含了return)。
  3. () -> {return "Mario";}
    这个Lambda没有参数,并返回String(利用显式返回语句)。
  • 使用案例
使用案例 Lambda示例
布尔表达式 (List<String> list) -> list.isEmpty()
创建对象 () -> new Apple(10)
消费一个对象 (Apple a) -> { System.out.println(a.getWeight());}
从一个对象中选择/抽取 (String s) -> s.length()
组合两个值 (int a, int b) -> a * b
比较两个对象 (Apple a1, Apple a2) - > a1.getWeight().compareTo(a2.getWeight())
  • 用处(函数式接口)
    所以在哪能用的上Lambda呢?
    可以在函数式接口上使用Lambda表达式

    函数式接口就是只定义一个抽象方法的接口
    Java API中本来就已经有的一些函数式接口
    例如:

         public interface Comparator<T> {
              int compare(T o1, T o2);
         } 
    
        public interface Runnable{
                void run();
         } 
    

    所以函数式接口可以干什么呢?

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。(Runnable是一个只定义了一个抽象方法run的函数式接口)

Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例!

使用Lambda:

    Runnable r1 = () -> System.out.println("Hello World 1");

使用匿名类:

  Runnable r2 = new Runnable(){
       public void run(){
             System.out.println("Hello World 2");
        }
   }; 
  • 函数描述符
    函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。

    例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。() -> void代表参数列表为空,且返回void的函数。这正是Runnable接口所代表的。

那么Lambad如何做类型检查呢?
Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。

List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);

image.png
  1. 找出filter方法的声明。
  2. 要求它是Predicate<Apple>(目标类型)对象的第二个正式参数。
  3. Predicate<Apple>是一个函数式接口,定义了一个叫作test的抽象方法。
  4. test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean。
  5. filter的任何实际参数都必须匹配这个要求。
    注意:如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句也必须与之匹配。

可以粗略的理解为Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样。

类型推断
Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。

没有类型推断:
Comparator<Apple> c =
 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 
有类型推断:
Comparator<Apple> c =
 (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); 
  • 使用局部变量
    Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。

原因:

  1. 实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
  2. 这一限制不鼓励你使用改变外部变量的典型命令式编程模式
  • 双冒号运算符:
    Lambda及其等效方法引用的例子:
Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(String s) -> System.out.println(s) System.out::println

构建方法引用

  1. 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
  2. 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)。
  3. 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。

构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:ClassName::new
---摘自《Java8实战》

相关文章

网友评论

      本文标题:Lambda表达式

      本文链接:https://www.haomeiwen.com/subject/kpoqsqtx.html