最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java Lambda 表達式源碼解析

 更新時間:2021年08月25日 17:03:13   作者:Yano-nankai  
這篇文章主要介紹了Java Lambda在JVM中是如何實現(xiàn)的,感興趣的小伙伴一起來了解了解

Java Lambda 源碼分析

問題:
Lambda 表達式是什么?JVM 內(nèi)部究竟是如何實現(xiàn) Lambda 表達式的?為什么要這樣實現(xiàn)?

一、基本概念

1、Lambda 表達式

下面的例子中,() -> System.out.println("1") 就是一個 Lambda 表達式。Java 8 中每一個 Lambda 表達式必須有一個函數(shù)式接口與之對應。Lambda 表達式就是函數(shù)式接口的一個實現(xiàn)。

@Test
public void test0() {
    Runnable runnable = () -> System.out.println("1");
    runnable.run();

    ToIntBiFunction<Integer, Integer> function = (n1, n2) -> n1 + n2;
    System.out.println(function.applyAsInt(1, 2));

    ToIntBiFunction<Integer, Integer> function2 = Integer::sum;
    System.out.println(function2.applyAsInt(1, 2));
}

大致形式就是 (param1, param2, param3, param4…) -> { doing…… };

2、函數(shù)式接口

首先要從 FunctionalInterface 注解講起,詳情見Annotation Type FunctionalInterface。

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 default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of 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 java.lang.Object or elsewhere.

簡單總結(jié)一下函數(shù)式接口的特征:

  • FunctionalInterface 注解標注一個函數(shù)式接口,不能標注類,方法,枚舉,屬性這些。
  • 如果接口被標注了 @FunctionalInterface,這個類就必須符合函數(shù)式接口的規(guī)范。
  • 即使一個接口沒有標注 @FunctionalInterface,如果這個接口滿足函數(shù)式接口規(guī)則,依舊可以被當作函數(shù)式接口。

注意:interface 中重寫 Object 類中的抽象方法,不會增加接口的方法數(shù),因為接口的實現(xiàn)類都是 Object 的子類。

我們可以看到 Runnable 接口,里面只有一個抽象方法 run(),則這個接口就是一個函數(shù)式接口。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

3、方法引用

所謂方法引用,是指如果某個方法簽名和接口恰好一致,就可以直接傳入方法引用。文章開頭的示例中,下面這塊代碼就是方法引用。

ToIntBiFunction<Integer, Integer> function2 = Integer::sum;

java.lang.Integer#sum 的實現(xiàn)如下:

public static int sum(int a, int b) {
    return a + b;
}

比如我們計算一個 Stream 的和,可以直接傳入 Integer::sum 這個方法引用。

@Test
public void test1() {
    Integer sum = IntStream.range(0, 10).boxed().reduce(Integer::sum).get();
    System.out.println(sum);
}

上面的代碼中,為什么可以直接在 reduce 方法中傳入 Integer::sum 這個方法引用呢?這是因為 reduce 方法的入?yún)⒕褪?BinaryOperator 的函數(shù)式接口。

Optional<T> reduce(BinaryOperator<T> accumulator);

BinaryOperator 是繼承自 BiFunction,定義如下:

@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

可以看到,只要是符合 R apply(T t, U u); 的方法引用,都可以傳入 reduce 中??梢允巧厦娲a中的 Integer::sum,也可以是 Integer::max。

二、深入實現(xiàn)原理

1、字節(jié)碼

首先寫 2 個 Lambda 方法:

public class LambdaMain {

    public static void main(String[] args) {
        new Thread(() -> System.out.println("1")).start();
        IntStream.range(0, 5).boxed().filter(i -> i < 3).map(i -> i + "").collect(Collectors.toList());
    }
}

之后 javac LambdaMain.java 編譯成字節(jié)碼文件,再通過 javap -p LambdaMain 輸出 class 文件的所有類和成員,得到輸出結(jié)果:

Compiled from "LambdaMain.java"
public class test.jdk.LambdaMain {
  public test.jdk.LambdaMain();
  public static void main(java.lang.String[]);
  private static java.lang.String lambda$main$2(java.lang.Integer);
  private static boolean lambda$main$1(java.lang.Integer);
  private static void lambda$main$0();
}
  • 輸出的 void lambda$main$0() 對應的是 () -> System.out.println("1")
  • 輸出的 boolean lambda$main$1(java.lang.Integer) 對應的是 i -> i < 3
  • 輸出的 java.lang.String lambda$main$2(java.lang.Integer) 對應的是 i -> i + ""

我們可以看出 Lambda 表達式在 Java 8 中首先會生成一個私有的靜態(tài)函數(shù)。

2、為什么不使用匿名內(nèi)部類?

如果要在 Java 語言中實現(xiàn) lambda 表達式,生成匿名內(nèi)部類就可以輕松實現(xiàn)。但是 JDK 為什么沒有這么實現(xiàn)呢?這是因為匿名內(nèi)部類有一些缺點。

  1. 每個匿名內(nèi)部類都會在編譯時創(chuàng)建一個對應的class 文件,在運行時不可避免的會有加載、驗證、準備、解析、初始化等類加載過程。
  2. 每次調(diào)用都會創(chuàng)建一個這個匿名內(nèi)部類 class 的實例對象,無論是有狀態(tài)的(使用到了外部的變量)還是無狀態(tài)(沒有使用外部變量)的內(nèi)部類。

3、invokedynamic

本來要寫文字的,但是俺發(fā)現(xiàn)俺總結(jié)的思維導圖還挺清晰的,直接提出來吧,囧。

 

 

詳情見 Class LambdaMetafactory 官方文檔,java.lang.invoke.LambdaMetafactory#metafactory 的實現(xiàn)。

public static CallSite metafactory(MethodHandles.Lookup caller,
                                    String invokedName,
                                    MethodType invokedType,
                                    MethodType samMethodType,
                                    MethodHandle implMethod,
                                    MethodType instantiatedMethodType)
        throws LambdaConversionException {
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                            invokedName, samMethodType,
                                            implMethod, instantiatedMethodType,
                                            false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
}

其主要的概念有如下幾個:

  • invokedynamic 字節(jié)碼指令:運行時 JVM 第一次到某個地方的這個指令的時候會進行 linkage,會調(diào)用用戶指定的 Bootstrap Method 來決定要執(zhí)行什么方法,之后便不需要這個步驟。
  • Bootstrap Method: 用戶可以自己編寫的方法,最終需要返回一個 CallSite 對象。
  • CallSite: 保存 MethodHandle 的容器,里面有一個 target MethodHandle。
  • MethodHandle: 真正要執(zhí)行的方法的指針。

測試一下 Lambda 函數(shù)生成的字節(jié)碼,為了方便起見,java 代碼改成如下:

public class LambdaMain {

    public static void main(String[] args) {
        new Thread(() -> System.out.println("1")).start();
    }
}

先編譯成 class 文件,之后再反匯編 javap -c -p LambdaMain 看下輸出:

Compiled from "LambdaMain.java"
public class test.jdk.LambdaMain {
  public test.jdk.LambdaMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Thread
       3: dup
       4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      12: invokevirtual #5                  // Method java/lang/Thread.start:()V
      15: return

  private static void lambda$main$0();
    Code:
       0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #7                  // String 1
       5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

可以看到 Thread 里的 Runnable 實現(xiàn)是通過 invokedynamic 調(diào)用的。Lambda 表達式在 Java 中最終編譯成私有的靜態(tài)函數(shù),JDK 最終使用 invokedynamic 字節(jié)碼指令調(diào)用。

以上就是Java Lambda 表達式源碼解析的詳細內(nèi)容,更多關于Java Lambda的資料請關注腳本之家其它相關文章!,希望大家以后多多支持腳本之家!

相關文章

  • 使用Spring Cloud Feign遠程調(diào)用的方法示例

    使用Spring Cloud Feign遠程調(diào)用的方法示例

    這篇文章主要介紹了使用Spring Cloud Feign遠程調(diào)用的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • Spring Boot 2.0 配置屬性自定義轉(zhuǎn)換的方法

    Spring Boot 2.0 配置屬性自定義轉(zhuǎn)換的方法

    這篇文章主要介紹了Spring Boot 2.0 配置屬性自定義轉(zhuǎn)換的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • SpringCloud服務接口調(diào)用OpenFeign及使用詳解

    SpringCloud服務接口調(diào)用OpenFeign及使用詳解

    這篇文章主要介紹了SpringCloud服務接口調(diào)用——OpenFeign,在學習Ribbon時,服務間調(diào)用使用的是RestTemplate+Ribbon實現(xiàn),而Feign在此基礎上繼續(xù)進行了封裝,使服務間調(diào)用變得更加方便,需要的朋友可以參考下
    2023-04-04
  • 詳解Java?token主流框架之JWT

    詳解Java?token主流框架之JWT

    JWT(JSON?Web?Token)是一種基于JSON格式的輕量級的、用于身份認證的開放標準,它通過在用戶和服務器之間傳遞一個安全的、可靠的、獨立的JSON對象來進行身份驗證和授權(quán),本文將詳細給大家介紹Java?token主流框架之JWT,需要的朋友可以參考下
    2023-05-05
  • 解決SpringBoot中使用@Transactional注解遇到的問題

    解決SpringBoot中使用@Transactional注解遇到的問題

    這篇文章主要介紹了SpringBoot中使用@Transactional注解遇到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 如何使用axis調(diào)用WebService及Java?WebService調(diào)用工具類

    如何使用axis調(diào)用WebService及Java?WebService調(diào)用工具類

    Axis是一個基于Java的Web服務框架,可以用來調(diào)用Web服務接口,下面這篇文章主要給大家介紹了關于如何使用axis調(diào)用WebService及Java?WebService調(diào)用工具類的相關資料,需要的朋友可以參考下
    2023-04-04
  • 詳解Java如何實現(xiàn)企業(yè)微信審批流程

    詳解Java如何實現(xiàn)企業(yè)微信審批流程

    這篇文章主要使用了一個Java示例代碼,來向大家展示如何在企業(yè)微信中實現(xiàn)審批流程,文中的示例代碼簡潔易懂,有需要的小伙伴可以參考下
    2024-11-11
  • 在IDEA中如何設置最多顯示文件標簽個數(shù)

    在IDEA中如何設置最多顯示文件標簽個數(shù)

    在使用IDEA進行編程時,可能會同時打開多個文件,當文件過多時,文件標簽會占據(jù)大部分的IDEA界面,影響我們的編程效率,因此,我們可以通過設置IDEA的文件標簽顯示個數(shù),來優(yōu)化我們的編程環(huán)境,具體的設置方法如下
    2024-10-10
  • 如何在java 8 stream表達式實現(xiàn)if/else邏輯

    如何在java 8 stream表達式實現(xiàn)if/else邏輯

    這篇文章主要介紹了如何在java 8 stream表達式實現(xiàn)if/else邏輯,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • Java使用POI實現(xiàn)導出Excel的方法詳解

    Java使用POI實現(xiàn)導出Excel的方法詳解

    在項目開發(fā)中往往需要使用到Excel的導入和導出,導入就是從Excel中導入到DB中,而導出就是從DB中查詢數(shù)據(jù)然后使用POI寫到Excel上。本文將利用POI實現(xiàn)導出Excel,需要的可以參考一下
    2022-10-10

最新評論

淮南市| 昌平区| 安义县| 晋江市| 仁化县| 连州市| 柯坪县| 克什克腾旗| 水城县| 柳江县| 昆明市| 灌阳县| 定兴县| 琼结县| 枝江市| 高青县| 博客| 中方县| 苏州市| 萨迦县| 江都市| 乌审旗| 喀什市| 徐汇区| 吐鲁番市| 岑巩县| 湄潭县| 台中市| 牟定县| 静海县| 绥化市| 马边| 六安市| 高安市| 湛江市| 康马县| 甘孜县| 都江堰市| 闻喜县| 临城县| 积石山|