专栏名称: 朱小厮的博客
著有畅销书:《深入理解Kafka》和《RabbitMQ实战指南》。公众号主要用来分享Java技术栈、Golang技术栈、消息中间件(如Kafka、RabbitMQ)、存储、大数据以及通用型技术架构等相关的技术。
目录
相关文章推荐
幸福成长札记  ·  第81天在闲鱼上卖货,赚钱才是最好的自我教育 ·  昨天  
幸福成长札记  ·  第81天在闲鱼上卖货,赚钱才是最好的自我教育 ·  昨天  
苏州新闻  ·  正在公示!恭喜苏州这些高校! ·  2 天前  
苏州新闻  ·  正在公示!恭喜苏州这些高校! ·  2 天前  
电子商务研究中心  ·  Temu韩国放大招 亚马逊涉嫌逃税遭调查 ... ·  2 天前  
亿邦动力  ·  Temu在韩国推本对本业务;SHEIN日本站 ... ·  2 天前  
抖音电商营销观察  ·  首届抖音商城好2节!一起来「玩个很新的东西」 ·  3 天前  
51好读  ›  专栏  ›  朱小厮的博客

你真的了解 lambda 吗(纠错篇)?

朱小厮的博客  · 公众号  ·  · 2019-07-17 08:42

正文

点击上方“ 朱小厮的博客 ”,选择“ 设为星标

做积极的人,而不是积极废人


来源: http://uee.me/aWWS2


1. 用法


示例:最普遍的一个例子,执行一个线程

  1. new Thread(() -> System.out.print("hello world")).start();


-> 我们发现它指向的是 Runnable接口

  1. @FunctionalInterface

  2. public interface Runnable {

  3.    /**

  4.     * When an object implementing interface Runnable is used

  5.     * to create a thread, starting the thread causes the object's

  6.     * run method to be called in that separately executing

  7.     * thread.

  8.     *

  9.     * The general contract of the method run is that it may

  10.     * take any action whatsoever.

  11.     *

  12.     * @see     java.lang.Thread#run()

  13.     */

  14.    public abstract void run();

  15. }


分析

  1. ->这个箭头是lambda表达式的关键操作符

  2. ->把表达式分成两截,前面是函数参数,后面是函数体。

  3. Thread的构造函数接收的是一个Runnable接口对象,而我们这里的用法相当于是把一个函数当做接口对象传递进去了,这点理解很关键,这正是函数式编程的含义所在。

  4. 我们注意到Runnable有个注解 @FunctionalInterface,它是jdk8才引入,它的含义是函数接口。它是lambda表达式的协议注解,这个注解非常重要,后面做源码分析会专门分析它的官方注释,到时候一目了然。


  1. /* @jls 4.3.2. The Class Object

  2. * @jls 9.8 Functional Interfaces

  3. * @jls 9.4.3 Interface Method Body

  4. * @since 1.8

  5. */

  6. @Documented

  7. @Retention(RetentionPolicy.RUNTIME)

  8. @Target(ElementType.TYPE)

  9. public @interface FunctionalInterface {}


2. 由此引发的一些案例


有参数有返回值的实例:集合排序

  1. List<String> list = new ArrayList<>();

  2. Collections.sort(list, (o1, o2) -> {

  3.    if(o1.equals(o2)) {

  4.        return 1;

  5.    }

  6.    return -1;

  7. })


我们知道Collections.sort方法的第二个参数接受的是一个 Comparator 的对象,它的部分关键源码是这样的

  1. @FunctionalInterface

  2. public interface Comparator {

  3.    int compare(T o1, T o2);

  4. }


如上已经去掉注释和部分其他方法。


我们可以看到sort的第二个参数是Comparator的compare方法,参数类型是T,分别是o1和o2,返回值是一个int。


3. 疑问


  • 上面的示例我们看到接口都有个 @FunctionalInterface的注解,但是我们在实际编程中并没有加这个注解也可以实现lambda表达式,例如:


  1. public class Main {


  2. interface ITest {

  3. int test(String string);

  4.       }


  5. static void Print(ITest test) {

  6. test.test("hello world");

  7.       }


  8. public static void main(String[] args) {

  9.     Print(string -> {

  10.          System.out.println(string);

  11.        return 0;

  12.           });

  13.       }

  14.   }


如上所示,确实不需要增加 @FunctionInterface注解就可以实现。


  • 如果在1中的示例的ITest接口中增加另外一个接口方法,我们会发现不能再用lambda表达式。


我们带着这两个疑问来进入源码解析。


4. 源码解析


必须了解注解 @FunctionInterface

上源码:

  1. package java.lang;


  2. import java.lang.annotation.*;


  3. /**

  4. * An informative annotation type used to indicate that an interface

  5. * type declaration is intended to be a functional interface as

  6. * defined by the Java Language Specification.

  7. *

  8. * Conceptually, a functional interface has exactly one abstract

  9. * method.  Since {@linkplain java.lang.reflect.Method#isDefault()

  10. * default methods} have an implementation, they are not abstract.  If

  11. * an interface declares an abstract method overriding one of the

  12. * public methods of {@code java.lang.Object}, that also does

  13. * not count toward the interface's abstract method count

  14. * since any implementation of the interface will have an

  15. * implementation from {@code java.lang.Object} or elsewhere.

  16. *

  17. *

    Note that instances of functional interfaces can be created with

  18. * lambda expressions, method references, or constructor references.

  19. *

  20. *

    If a type is annotated with this annotation type, compilers are

  21. * required to generate an error message unless:

  22. *

  23. *

    • *

    • The type is an interface type and not an annotation type, enum, or class.
    • *

    • The annotated type satisfies the requirements of a functional interface.
    • *

    • *

    • *

      However, the compiler will treat any interface meeting the

    • * definition of a functional interface as a functional interface

    • * regardless of whether or not a {@code FunctionalInterface}

    • * annotation is present on the interface declaration.

    • *

    • * @jls 4.3.2. The Class Object

    • * @jls 9.8 Functional Interfaces

    • * @jls 9.4.3 Interface Method Body

    • * @since 1.8

    • */

    • @Documented

    • @Retention(RetentionPolicy.RUNTIME)

    • @Target(ElementType.TYPE)

    • public @interface FunctionalInterface {}


    我们说过这个注解用来规范lambda表达式的使用协议的,那么注释中都说了哪些呢?


    • 一种给interface做注解的注解类型,被定义成java语言规范。

    * An informative annotation type used to indicate that an functional interface

    * type declaration is intended to be a functional interface as

    * defined by the Java Language Specification.


    • 一个被它注解的接口只能有一个抽象方法,有两种例外。


    第一是接口允许有实现的方法。

    这种实现的方法是用default关键字来标记的

    (java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法),例如

    当然这是jdk8才引入的特性,到此我们才知道,知识是一直在变化的,我们在学校中学到interface接口不允许有实现的方法是错误的,随着时间推移,一切规范都有可能发生变化。


    如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则:一个被它注解的接口只能有一个抽象方法


    例如同样是Compartor接口中,它重新声明了equals方法:


    这些是对如下注释的翻译和解释。

    1. * Conceptually, a functional interface has exactly one abstract

    2. * method.  Since {@linkplain java.lang.reflect.Method#isDefault()

    3. * default methods} have an implementation, they are not abstract.  If

    4. * an interface declares an abstract method overriding one of the

    5. * public methods of {@code java.lang.Object}, that also does

    6. * not count toward the interface's abstract method count

    7. * since any implementation of the interface will have an

    8. * implementation from {@code java.lang.Object} or elsewhere.


    • 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件:

    1. 这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class

    2. 这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。


    1. *

      If a type is annotated with this annotation type, compilers are

    2. * required to generate an error message unless:

    3. *

    4. *

      • *

      • The type is an interface type and not an annotation type, enum, or class.

      • *

      • The annotated type satisfies the requirements of a functional interface.

      • *


      • 编译器会自动把满足function interface要求的接口自动识别为function interface,所以你才不需要对上面示例中的 ITest接口增加@FunctionInterface注解。

      1. *

        However, the compiler will treat any interface meeting the

      2. * definition of a functional interface as a functional interface

      3. * regardless of whether or not a {@code FunctionalInterface}

      4. * annotation is present on the interface declaration.


      通过了解function interface我们能够知道怎么才能正确的创建一个function interface来做lambda表达式了。接下来的是了解java是怎么把一个函数当做一个对象作为参数使用的。


      5. 穿越:对象变身函数


      让我们重新复盘一下上面最开始的实例:

      1. new Thread(() -> System.out.print("hello world")).start();


      我们知道在jdk8以前我们都是这样来执行的:

      1. Runnable r = new Runnable(){

      2.    System.out.print("hello world");

      3. };

      4. new Thread(r).start();


      我们知道两者是等价的,也就是说 r 等价于 ()->System.out.print("hello world"),一个接口对象等于一个lambda表达式?那么lambda表达式肯定做了这些事情(未看任何资料,纯粹推理,有误再改正):

      1. 创建接口对象

      2. 实现接口对象

      3. 返回接口对象


      6. 关于 UnaryOperator


      上篇文章(聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator)关于 UnaryOperator草草收尾,在这里给大家重新梳理一下,关于它的使用场景以及它与lambda表达式的关系。


      • 使用场景

      要先理解它的作用,它是接受一个参数并返回与该类型同的值,来看一个List怎么用它的,java.util.List中的replaceAll就用它了:

      1.    default void replaceAll(UnaryOperator operator) {

      2.        Objects.requireNonNull(operator);

      3.        final ListIterator li = this.listIterator();

      4.        while (li.hasNext()) {

      5.            li.set( operator.apply(li.next()));

      6.        }

      7.    }


      我们可以看到这个方法的目的是把list中的值经过operator操作后重新返回一个新值,例如具体调用。

      1.        List<String> list = new ArrayList<>();

      2.        list.add("abc");

      3.        list.replaceAll(s -> s + "efg");


      4.        System.out.println(list);


      其中lambda表达式 s->s+"efg"就是这个operator对象,那么最终list中的值就变成了["abcefg"],由此我们可以知道它的作用就是对输入的值再加工,并返回同类型的值,怎么用就需要你自己扩展发挥了。


      • 与lambda表达式的关系?

      在我看来,它跟lambda表达式的关系并不大,只是它是jdk内置的一种标准操作,类似的二元操作符 BinaryOperator它可以接受两个同类型参数,并返回同类型参数的值。


      7. 关于UnaryOperator,我们百尺竿头更进一步,深入到核心


      先贴出它的源码:

      1. @FunctionalInterface

      2. public interface UnaryOperator extends Function {


      3.    /**

      4.     * Returns a unary operator that always returns its input argument.

      5.     *

      6.     * @param the type of the input and output of the operator

      7.     * @return a unary operator that always returns its input argument

      8.     */

      9.    static UnaryOperator identity() {

      10.        return t -> t;

      11.    }

      12. }







      请到「今天看啥」查看全文