Objects::nonNull 和 x -> x != null 之间有什么区别吗?

2022-09-03 04:10:40

请考虑以下类:

import java.util.Objects;
import java.util.function.Predicate;

public class LambdaVsMethodRef {
    public static void main(String[] args) {
        Predicate<Object> a = Objects::nonNull;
        Predicate<Object> b = x -> x != null;
    }
}

第一个谓词是从方法引用创建的,另一个谓词是从 lambda 表达式创建的。这些谓词具有相同的行为(的主体只是 )。lambda 缩短了两个字符(可能允许流管道适合一行)。nonNullreturn obj != null;

除了代码样式之外,Objects::nonNullx -> x != null 之间有什么区别吗?换句话说,我应该更喜欢一个而不是另一个吗?

lambda-dev 和 lambda-libs-spec-{observers,experts} 邮件列表消息提到 和 (早期名称) 没有解决这一点。(我很惊讶没有人质疑添加Objects方法,因为它们可以很容易地用lambda替换,但另一方面,也是如此。isNullnonNullisNotNullInteger::sum

我还用.唯一的区别是传递给 lambda 元工厂引导方法的方法句柄:javap

  BootstrapMethods:
0: #16 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #17 (Ljava/lang/Object;)Z
    #18 invokestatic java/util/Objects.nonNull:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z
1: #16 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #17 (Ljava/lang/Object;)Z
    #20 invokestatic LambdaVsMethodRef.lambda$main$1:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z

当然,元工厂可以根据JVM的心血来潮为方法引用和lambda做不同的事情,所以这并不能证明什么。


答案 1

如您所见,lambda 和方法引用的语义实际上是相同的。我很难想到任何实际的可观察到的差异,除了使用反射或类似的东西来深入研究课堂。x -> x != nullObjects::nonNull

与 lambda 相比,使用方法引用具有较小的空间优势。对于 lambda,lambda 的代码被编译成包含类的私有静态方法,然后通过引用此静态方法调用 lambda 元工厂。在方法引用情况下,该方法已存在于类中,因此 lambda 元工厂是使用对现有方法的引用来调用的。这样可以节省适度的空间。java.util.Objects

考虑以下小类:

class LM { // lambda
    static Predicate<Object> a = x -> x != null;
}

class MR { // method reference
    static Predicate<Object> a = Objects::nonNull;
}

(感兴趣的读者应该跑去看这些编译方式之间的详细差异。javap -private -cp classes -c -v <class>

这导致 lambda 事例为 1,094 字节,方法参考事例为 989 字节。(Javac 1.8.0_11。这不是一个巨大的区别,但是如果您的程序可能具有大量这样的lambda,则可以考虑使用方法引用所节省的空间。

此外,与 lambda 相比,方法引用更有可能被 JIT 编译和内联,因为方法引用可能使用得更多。这可能会导致微小的性能改进。不过,这似乎不太可能产生实际影响。

虽然你特别说过“代码风格以外的......”这真的主要是关于风格。这些小方法被专门添加到API中,以便程序员可以使用名称而不是内联lambda。这通常会提高代码的可理解性。另一点是,方法引用通常具有显式类型信息,这些信息可以在困难的类型推断情况下提供帮助,例如嵌套比较器。(这并不真正适用于。添加强制转换或显式类型的 lambda 参数会增加很多混乱,因此在这些情况下,方法引用显然是赢家。Objects::nonNull


答案 2

推荐