你真的了解java的lambda吗?- java lambda用法与源码分析

本文详细解析了Java 8中lambda表达式的用法与原理,探讨了@FunctionalInterface注解的重要性及其背后的意义,同时介绍了如何使用UnaryOperator及它与lambda表达式之间的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用法

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

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

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

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface Runnable is used
     * to create a thread, starting the thread causes the object's
     * run method to be called in that separately executing
     * thread.
     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

分析

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

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

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

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

    /* @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 {}

由此引发的一些案例

有参数有返回值的实例:集合排序
List list = new ArrayList

疑问

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

    public class Main {
    
    
    interface ITest {
        int test(String string);
    }
    
    static void Print(ITest test) {
        test.test("hello world");
    }
    
    public static void main(String[] args) {
        Print(string -> {
            System.out.println(string);
            return 0;
        });
    }
    
    }

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

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

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

源码解析

必须了解注解 @FunctionInterface

上源码:

package java.lang;

import java.lang.annotation.*;

/**
 * An informative annotation type used to indicate that an interface
 * type declaration is intended to be a functional interface as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * not count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 *
 * 

Note that instances of functional interfaces can be created with * lambda expressions, method references, or constructor references. * *

If a type is annotated with this annotation type, compilers are * required to generate an error message unless: * *

  • *
  • 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 表达式的使用协议的,那么注释中都说了哪些呢?

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

    * An informative annotation type used to indicate that an interface

    • type declaration is intended to be a functional interface as
    • defined by the Java Language Specification.
  2. 一个被它注解的接口只能有一个抽象方法,有两种例外。

    1. 第一是接口允许有实现的方法,这种实现的方法是用 default 关键字来标记的(java 反射中 java.lang.reflect.Method#isDefault() 方法用来判断是否是 default 方法),例如:

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

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

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

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

    * Conceptually, a functional interface has exactly one abstract

    • method. Since {@linkplain java.lang.reflect.Method#isDefault()
    • default methods} have an implementation, they are not abstract. If
    • an interface declares an abstract method overriding one of the
    • public methods of {@code java.lang.Object}, that also does
    • not count toward the interface’s abstract method count
    • since any implementation of the interface will have an
    • implementation from {@code java.lang.Object} or elsewhere.
  3. 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件

    1. 这个类型必须是一个 interface,而不是其他的注解类型、枚举 enum 或者类 class
    2. 这个类型必须满足 function interface 的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。

    * 

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

    • required to generate an error message unless:
      *
      • 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 注解。

      * 

      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.

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

    穿越:对象变身函数

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

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

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

    Runnable r = new Runnable(){
        System.out.print("hello world");
    };
    new Thread(r).start();
    

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

    1. 创建接口对象
    2. 实现接口对象
    3. 返回接口对象

    关于UnaryOperator

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

    使用场景

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

    default void replaceAll(UnaryOperator operator) {
        Objects.requireNonNull(operator);
        final ListIterator li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    

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

    List list = new ArrayList
    与 lambda 表达式的关系?

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

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

    先贴出它的源码:

    @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
    
        /**
         * Returns a unary operator that always returns its input argument.
         *
         * @param <T> the type of the input and output of the operator
         * @return a unary operator that always returns its input argument
         */
        static <T> UnaryOperator<T> identity() {
            return t -> t;
        }
    }

    我们看到这个 function interface 居然没有抽象方法,不,不是没有,我们继续看 Function接口

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    
        /**
         * Returns a composed function that first applies the {@code before}
         * function to its input, and then applies this function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         *
         * @param <V> the type of input to the {@code before} function, and to the
         *           composed function
         * @param before the function to apply before this function is applied
         * @return a composed function that first applies the {@code before}
         * function and then applies this function
         * @throws NullPointerException if before is null
         *
         * @see #andThen(Function)
         */
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        /**
         * Returns a composed function that first applies this function to
         * its input, and then applies the {@code after} function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         *
         * @param <V> the type of output of the {@code after} function, and of the
         *           composed function
         * @param after the function to apply after this function is applied
         * @return a composed function that first applies this function and then
         * applies the {@code after} function
         * @throws NullPointerException if after is null
         *
         * @see #compose(Function)
         */
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        /**
         * Returns a function that always returns its input argument.
         *
         * @param <T> the type of the input and output objects to the function
         * @return a function that always returns its input argument
         */
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }

    既然他们都被注解为@FunctionInterface了,那么他们肯定有一个唯一的抽象方法,那就是apply

    我们知道->lambda 表达式它是不需要关心函数名字的,所以不管它叫什么,apply也好,apply1也好都可以,但 jdk 肯定要叫一个更加合理的名字,那么我们知道s -> s + "efg"->调用的就是apply方法

    而且我们注意到这里有一个identity()的静态方法,它返回一个 Function 对象,它其实跟 lambda 表达式关系也不大,它的作用是返回当前 function 所要表达的 lambda 含义。相当于创建了一个自身对象。

    Function 算是 lambda 的一种扩展应用,这个 Function 的的作用是Represents a function that accepts one argument and produces a result.意思是接受一个参数,并产生(返回)一个结果(类型可不同)。

    类似的还有很多 Function,都在包 java.util.Function 中

    你也可以创建自己的 Function,它是用来表达操作是怎样的。如传入的参数是什么,返回的是什么。

    其实你只要明白它抽象的是操作就可以了。

    到此就知道,原来 UnaryOperator 没啥神秘的,jdk 把这些操作放在 java.util.function 中也正说明了它是一个工具类,是为了提取重复代码,让它可以重用,毕竟需要用到这样的操作的地方太多了,提取是有必要的。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值