亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java Lambda 表達(dá)式源碼解析

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

Java Lambda 源碼分析

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

一、基本概念

1、Lambda 表達(dá)式

下面的例子中,() -> System.out.println("1") 就是一個(gè) Lambda 表達(dá)式。Java 8 中每一個(gè) Lambda 表達(dá)式必須有一個(gè)函數(shù)式接口與之對(duì)應(yīng)。Lambda 表達(dá)式就是函數(shù)式接口的一個(gè)實(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 注解講起,詳情見(jiàn)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ǎn)單總結(jié)一下函數(shù)式接口的特征:

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

注意:interface 中重寫(xiě) Object 類中的抽象方法,不會(huì)增加接口的方法數(shù),因?yàn)榻涌诘膶?shí)現(xiàn)類都是 Object 的子類。

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

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

3、方法引用

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

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

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

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

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

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

上面的代碼中,為什么可以直接在 reduce 方法中傳入 Integer::sum 這個(gè)方法引用呢?這是因?yàn)?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。

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

1、字節(jié)碼

首先寫(xiě) 2 個(gè) 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é)碼文件,再通過(guò) 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() 對(duì)應(yīng)的是 () -> System.out.println("1")
  • 輸出的 boolean lambda$main$1(java.lang.Integer) 對(duì)應(yīng)的是 i -> i < 3
  • 輸出的 java.lang.String lambda$main$2(java.lang.Integer) 對(duì)應(yīng)的是 i -> i + ""

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

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

如果要在 Java 語(yǔ)言中實(shí)現(xiàn) lambda 表達(dá)式,生成匿名內(nèi)部類就可以輕松實(shí)現(xiàn)。但是 JDK 為什么沒(méi)有這么實(shí)現(xiàn)呢?這是因?yàn)槟涿麅?nèi)部類有一些缺點(diǎn)。

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

3、invokedynamic

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

 

 

詳情見(jiàn) Class LambdaMetafactory 官方文檔,java.lang.invoke.LambdaMetafactory#metafactory 的實(shí)現(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();
}

其主要的概念有如下幾個(gè):

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

測(cè)試一下 Lambda 函數(shù)生成的字節(jié)碼,為了方便起見(jiàn),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 實(shí)現(xiàn)是通過(guò) invokedynamic 調(diào)用的。Lambda 表達(dá)式在 Java 中最終編譯成私有的靜態(tài)函數(shù),JDK 最終使用 invokedynamic 字節(jié)碼指令調(diào)用。

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

相關(guān)文章

  • Java實(shí)現(xiàn)給Word文件添加文字水印

    Java實(shí)現(xiàn)給Word文件添加文字水印

    Word中設(shè)置水印時(shí),可預(yù)設(shè)的文字或自定義文字設(shè)置為水印效果,但通常添加水印效果時(shí),會(huì)對(duì)所有頁(yè)面都設(shè)置成統(tǒng)一效果。本文將利用Java給Word每一頁(yè)設(shè)置不同文字水印效果,需要的可以參考一下
    2022-02-02
  • SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能

    SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能

    這篇文章主要介紹了SpringBoot基于過(guò)濾器和內(nèi)存實(shí)現(xiàn)重復(fù)請(qǐng)求攔截,這里我們使用過(guò)濾器的方式對(duì)進(jìn)入服務(wù)器的請(qǐng)求進(jìn)行過(guò)濾操作,實(shí)現(xiàn)對(duì)相同客戶端請(qǐng)求同一個(gè)接口的過(guò)濾,需要的朋友可以參考下
    2023-01-01
  • JavaWeb實(shí)現(xiàn)打印功能

    JavaWeb實(shí)現(xiàn)打印功能

    這篇文章主要介紹了JavaWeb實(shí)現(xiàn)打印功能的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-05-05
  • 修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn)解析

    修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn)解析

    這篇文章主要介紹了修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn),即對(duì)影響外觀的theme跟style的相關(guān)修改,需要的朋友可以參考下
    2015-12-12
  • zuul集成Sentinel,完成對(duì)path映射的限流操作

    zuul集成Sentinel,完成對(duì)path映射的限流操作

    這篇文章主要介紹了zuul集成Sentinel,完成對(duì)path映射的限流操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Spring+EHcache緩存實(shí)例詳解

    Spring+EHcache緩存實(shí)例詳解

    這篇文章主要為大家詳細(xì)介紹了Spring+EHcache緩存實(shí)例,EhCache是一個(gè)純Java的進(jìn)程內(nèi)緩存框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • 用Java實(shí)現(xiàn)春聯(lián)?支持自定義字體顏色

    用Java實(shí)現(xiàn)春聯(lián)?支持自定義字體顏色

    大家好,本篇文章主要講的是用Java編寫(xiě)春聯(lián)?支持自定義字體顏色,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-01-01
  • Spring注解之@Import的簡(jiǎn)單介紹

    Spring注解之@Import的簡(jiǎn)單介紹

    @Import是Spring基于Java注解配置的主要組成部分,下面這篇文章主要給大家介紹了關(guān)于Spring注解之@Import的簡(jiǎn)單介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • 聊聊Java的switch為什么不支持long

    聊聊Java的switch為什么不支持long

    這篇文章主要介紹了Java的switch為什么不支持long,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 基于zipoutputStream的簡(jiǎn)單使用

    基于zipoutputStream的簡(jiǎn)單使用

    這篇文章主要介紹了基于zipoutputStream的簡(jiǎn)單使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12

最新評(píng)論