栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

lambda和方法引用在运行时级别之间有什么区别?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

lambda和方法引用在运行时级别之间有什么区别?

入门

为了对此进行研究,我们从以下类开始:

import java.io.Serializable;import java.util.Comparator;public final class Generic {    // Bad implementation, only used as an example.    public static final Comparator<Integer> COMPARATOR = (a, b) -> (a > b) ? 1 : -1;    public static Comparator<Integer> reference() {        return (Comparator<Integer> & Serializable) COMPARATOR::compare;    }    public static Comparator<Integer> explicit() {        return (Comparator<Integer> & Serializable) (a, b) -> COMPARATOR.compare(a, b);    }}

编译后,我们可以使用以下命令对其进行反汇编:

javap -c -p -s -v Generic.class

除去不相关的部分(以及其他一些杂物,例如完全限定的类型和的初始化

COMPARATOR
),我们剩下的就是

  public static final Comparator<Integer> COMPARATOR;  public static Comparator<Integer> reference();      0: getstatic     #2  // Field COMPARATOR:LComparator;          3: dup          4: invokevirtual #3   // Method Object.getClass:()LClass;          7: pop          8: invokedynamic #4,  0  // InvokeDynamic #0:compare:(LComparator;)LComparator;          13: checkcast     #5  // class Serializable          16: checkcast     #6  // class Comparator          19: areturn  public static Comparator<Integer> explicit();      0: invokedynamic #7,  0  // InvokeDynamic #1:compare:()LComparator;          5: checkcast     #5  // class Serializable          8: checkcast     #6  // class Comparator          11: areturn  private static int lambda$explicit$d34e1a25$1(Integer, Integer);     0: getstatic     #2  // Field COMPARATOR:LComparator;     3: aload_0     4: aload_1     5: invokeinterface #44,  3  // InterfaceMethod Comparator.compare:(LObject;LObject;)I    10: ireturnBootstrapMethods:      0: #61 invokestatic invoke/Lambdametafactory.altmetafactory:(Linvoke/MethodHandles$Lookup;LString;Linvoke/MethodType;[LObject;)Linvoke/CallSite;        Method arguments:          #62 (LObject;LObject;)I          #63 invokeinterface Comparator.compare:(LObject;LObject;)I          #64 (LInteger;LInteger;)I          #65 5          #66 0  1: #61 invokestatic invoke/Lambdametafactory.altmetafactory:(Linvoke/MethodHandles$Lookup;LString;Linvoke/MethodType;[LObject;)Linvoke/CallSite;        Method arguments:          #62 (LObject;LObject;)I          #70 invokestatic Generic.lambda$explicit$df5d232f$1:(LInteger;LInteger;)I          #64 (LInteger;LInteger;)I          #65 5          #66 0

立即我们看到该

reference()
方法的字节码与的字节码不同
explicit()
。但是,显着的差异实际上并不相关,但是引导方法很有趣。

invokedynamic调用站点通过 bootstrap方法 链接到一个方法,该 方法 是由编译器为动态类型语言指定的一种 方法
,该方法由JVM调用一次以链接该站点。

(对非Java语言的Java虚拟机支持,重点是它们的)

这是负责创建lambda使用的CallSite的代码。在

Methodarguments
下面的每个自举方法列出的是作为可变参数的参数(即,传递的值
args
的)LambdametaFactory#altmetaFactory。

方法参数的格式

  1. samMethodType-函数对象要实现的方法的签名和返回类型。
  2. implMethod-一个直接的方法句柄,描述在调用时应调用的实现方法(对参数类型,返回类型进行适当的调整,并在调用参数之前添加捕获的参数)。
  3. InstantiatedMethodType-应该在调用时动态强制执行的签名和返回类型。这可能与samMethodType相同,也可能是它的特殊化。
  4. 标志表示其他选项;这是所需标志的按位或。定义的标志是FLAG_BRIDGES,FLAG_MARKERS和FLAG_SERIALIZABLE。
  5. bridgeCount是功能对象应实现的其他方法签名的数量,并且仅当设置了FLAG_BRIDGES标志时才存在。

在这两种情况下,这里

bridgeCount
都是0,所以就没有6,否则将是
bridges
-要实现的其他方法签名的可变长度列表(假定
bridgeCount
为0,我不完全确定为什么设置了FLAG_BRIDGES)。

将以上内容与我们的论据相匹配,我们得到:

  1. 由于通用类型擦除,因此函数签名和返回类型
    (Ljava/lang/Object;Ljava/lang/Object;)I
    (即Comparator#compare的返回类型)。
  2. 调用此lambda时被调用的方法(不同)。
  3. lambda的签名和返回类型,将在调用lambda时进行检查:(
    (LInteger;LInteger;)I
    请注意,这些不会被擦除,因为这是lambda规范的一部分)。
  4. 这些标志,在两种情况下都是FLAG_BRIDGES和FLAG_SERIALIZABLE的组成(即5)。
  5. 桥接方法签名的数量,0。

我们可以看到为两个lambda都设置了FLAG_SERIALIZABLE,所以不是那样。

实施方法

方法参考lambda的实现方法为

Comparator.compare:(LObject;LObject;)I
,但显式lambda
的实现方法为
Generic.lambda$explicit$df5d232f$1:(LInteger;LInteger;)I
。查看反汇编,我们可以看到前者本质上是后者的内联版本。唯一的其他显着差异是方法参数类型(如前所述,这是由于通用类型擦除)。

Lambda何时可序列化?

如果lambda表达式的目标类型和捕获的参数可序列化,则可以对其进行序列化。

Lambda表达式(Java™教程)

其中重要的部分是“捕获的参数”。回头看一下反汇编的字节码,方法引用的invokedynamic指令肯定看起来像是在捕获Comparator(

#0:compare:(LComparator;)LComparator;
与显式lambda相反
#1:compare:()LComparator;
)。

确认捕获是问题

ObjectOutputStream
包含一个
extendedDebugInfo
字段,我们可以使用
-Dsun.io.serialization.extendedDebugInfo=true
VM参数设置该字段:

$ java -Dsun.io.serialization.extendedDebugInfo = true通用

当我们尝试再次序列化lambda时,这给出了非常令人满意的结果

Exception in thread "main" java.io.NotSerializableException: Generic$$Lambda$1/321001045        - element of array (index: 0)        - array (class "[LObject;", size: 1) - field (class "invoke.SerializedLambda", name: "capturedArgs", type: "class [LObject;") // <--- !!        - root object (class "invoke.SerializedLambda", SerializedLambda[capturingClass=class Generic, functionalInterfaceMethod=Comparator.compare:(LObject;LObject;)I, implementation=invokeInterface Comparator.compare:(LObject;LObject;)I, instantiatedMethodType=(LInteger;LInteger;)I, numCaptured=1])    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1182)        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)    at Generic.main(Generic.java:27)

实际发生了什么

从上面可以看出,显式lambda 不能 捕获任何内容,而方法引用lambda可以捕获任何内容。再次查看字节码可以清楚地看出这一点:

  public static Comparator<Integer> explicit();      0: invokedynamic #7,  0  // InvokeDynamic #1:compare:()LComparator;          5: checkcast     #5  // class java/io/Serializable          8: checkcast     #6  // class Comparator          11: areturn

如上所示,它具有以下实现方法:

  private static int lambda$explicit$d34e1a25$1(java.lang.Integer, java.lang.Integer);     0: getstatic     #2  // Field COMPARATOR:Ljava/util/Comparator;     3: aload_0     4: aload_1     5: invokeinterface #44,  3  // InterfaceMethod java/util/Comparator.compare:(Ljava/lang/Object;Ljava/lang/Object;)I    10: ireturn

显式lambda实际上正在调用

lambda$explicit$d34e1a25$1
,而后者又调用
COMPARATOR#compare
。间接层意味着它不会捕获任何不是的东西
Serializable
(确切地说是任何东西),因此可以安全地进行序列化。方法引用表达式
直接 使用
COMPARATOR
(然后将其值传递给bootstrap方法):

  public static Comparator<Integer> reference();      0: getstatic     #2  // Field COMPARATOR:LComparator;          3: dup          4: invokevirtual #3   // Method Object.getClass:()LClass;          7: pop          8: invokedynamic #4,  0  // InvokeDynamic #0:compare:(LComparator;)LComparator;          13: checkcast     #5  // class java/io/Serializable          16: checkcast     #6  // class Comparator          19: areturn

缺少间接意味着

COMPARATOR
必须与lambda一起序列化。由于
COMPARATOR
未引用
Serializable
值,因此失败。

解决方法

我很犹豫地将其称为编译器错误(我希望缺乏间接性可以起到优化作用),尽管这很奇怪。解决方法是微不足道的,但是很丑。添加

COMPARATOR
at声明的显式强制转换:

public static final Comparator<Integer> COMPARATOR = (Serializable & Comparator<Integer>) (a, b) -> a > b ? 1 : -1;

这使所有内容都能在Java
1.8.0_45上正确执行。还值得注意的是,eclipse编译器也会在方法参考案例中产生该间接层,因此本文中的原始代码不需要修改即可正确执行。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/486059.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号