当前位置: 首页 > 面试题库 >

方法引用缓存在Java 8中是个好主意吗?

逄宁
2023-03-14
问题内容

考虑我有如下代码

class Foo {

   Y func(X x) {...} 

   void doSomethingWithAFunc(Function<X,Y> f){...}

   void hotFunction(){
        doSomethingWithAFunc(this::func);
   }

}

假设hotFunction经常调用。那么建议缓存this::func如下:

class Foo {
     Function<X,Y> f = this::func;
     ...
     void hotFunction(){
        doSomethingWithAFunc(f);
     }
}

就我对java方法引用的理解而言,使用方法引用时,虚拟机会创建一个匿名类的对象。因此,缓存引用只会创建一次该对象,而第一种方法是在每个函数调用上创建该对象。它是否正确?

是应该缓存出现在代码中热门位置的方法引用,还是VM能够对其进行优化并使多余的缓存?是否有一般的最佳实践,或者这种高度VM实现是否特定于这种缓存是否有用?

Java 缓存 Java-8 方法参考


问题答案:

对于无状态Lambda或有状态Lambda ,必须区分相同调用站点的频繁执行和对同一方法的方法引用的频繁使用(由不同的调用站点)之间的区别。

看下面的例子:

    Runnable r1=null;
    for(int i=0; i<2; i++) {
        Runnable r2=System::gc;
        if(r1==null) r1=r2;
        else System.out.println(r1==r2? "shared": "unshared");
    }

在这里,相同的调用站点将执行两次,生成无状态的lambda,并且当前的实现将打印出来"shared"

Runnable r1=null;
for(int i=0; i<2; i++) {
  Runnable r2=Runtime.getRuntime()::gc;
  if(r1==null) r1=r2;
  else {
    System.out.println(r1==r2? "shared": "unshared");
    System.out.println(
        r1.getClass()==r2.getClass()? "shared class": "unshared class");
  }
}

在第二个示例中,同一调用站点被执行两次,生成一个包含对Runtime实例的引用的lambda,并且当前实现将打印出来,”unshared”但是”shared class”。

Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
    r1.getClass()==r2.getClass()? "shared class": "unshared class");

相反,在最后一个示例中,有两个不同的调用站点产生等效的方法引用,但从此开始,1.8.0_05它们将打印"unshared"和"unshared class"

对于每个lambda表达式或方法引用,编译器将发出一条invokedynamic指令,该指令引用该类中JRE提供的引导方法LambdaMetafactory以及生成所需lambda实现类所需的静态参数。元工厂生成的内容将留给实际的JRE,但这是invokedynamic指令的指定行为,可以记住并重新使用CallSite在第一次调用时创建的实例。

当前的JRE会为无状态lambda 生成一个ConstantCallSite包含一个MethodHandle对象的常量对象(并且没有可想象的理由进行不同的处理)。并且对方法的方法引用static始终是无状态的。因此,对于无状态的lambda和单个调用站点,答案必须是:不缓存,JVM可以缓存,如果不缓存,则必须有很强的理由不应该抵消。

对于具有参数this::funclambda ,并且是具有this实例引用的lambda,情况有所不同。允许JRE缓存它们,但这意味着Map在实际参数值和所得的lambda之间保持某种形式,这可能比再次创建简单的结构化lambda实例要昂贵得多。当前的JRE不缓存具有状态的Lambda实例。

但这并不意味着每次都会创建lambda类。这仅意味着已解析的调用站点将像普通的对象构造一样,实例化在第一次调用时生成的lambda类。

类似的情况适用于对由不同调用站点创建的相同目标方法的方法引用。JRE被允许在它们之间共享一个lambda实例,但是在当前版本中,它不允许共享,这很可能是因为不清楚缓存维护是否会奏效。在这里,即使生成的类也可能不同。

因此,像你的示例一样进行缓存可能会使你的程序做不同的事情。但不一定更有效。缓存的对象并不总是比临时对象更有效。除非你真正衡量了由lambda创建引起的性能影响,否则不应添加任何缓存。

我认为,仅在某些特殊情况下缓存可能有用:

  • 我们谈论的是许多使用相同方法的不同呼叫站点
  • lambda是在构造函数/类初始化中创建的,因为稍后在使用站点上将
  • 同时被多个线程调用
  • 遭受第一次调用性能降低的困扰


 类似资料:
  • 众所周知,创建NSDate格式器是“昂贵的” 甚至苹果的数据格式指南(2014-02年更新)也指出: 创建日期格式化程序并不是一项廉价的操作。如果您可能经常使用格式化程序,缓存单个实例通常比创建和处理多个实例更有效。一种方法是使用静态变量。 但该文档似乎并不是swift的最新版本,我在最新的NSDateFormatter类参考中也找不到任何关于缓存格式化程序的信息,所以我只能假设swift和obj

  • 问题内容: 我正在设计一个大型数据库。在我的应用程序中,我将有很多行,例如,我目前有一个包含400万条记录的表。我的大多数查询都使用datetime子句来选择数据。索引mysql数据库中的datetime字段是一个好主意吗? 我正在努力保持数据库正常运行,并且查询运行平稳 此外,您认为创建高效数据库应该有什么主意? 问题答案: MySQL建议出于各种原因使用索引,包括消除条件之间的行:http :

  • 问题内容: 例如,这是个好主意吗? 如果您在同一台服务器上有两个虚拟主机,一个虚拟主机,一个虚拟主机,并且使用不同的Apache DocumentRoots,则这将避免当include的来源未知并且可以在任何目录中时,不必包含绝对路径。 (注意:以下部分中的文件路径是相对于Web根目录的。实际上,它们类似于,其中Web根目录在哪里) 例如:我有一个/core/init.php,它是使用来自网站(,

  • 问题内容: 我已经多次听到您不应该执行或出于性能方面的考虑,但是无法深入了解有关它的更多信息。 我可以想象数据库随后将 所有 列都用于操作,这可能会导致性能下降,但是我不确定。有人有关于该主题的更多信息吗? 问题答案: 1.关于count(*)vs. count(其他) SQL是声明性的,您可以指定所需的 内容 。这不同于指定 如何 获得所需的东西。这意味着数据库引擎可以自由地以其认为最有效的方式

  • 主要内容:1 Java8 方法引用的介绍,2 Java8 方法引用的分类,3 Java8 方法引用:引用静态方法,4 Java8 方法引用:引用实例方法,5 Java8 方法引用:引用构造方法1 Java8 方法引用的介绍 Java提供了一个新功能,称为Java 8中的方法引用。方法引用用于引用功能接口的方法。它是lambda表达式的紧凑和简单形式。每次使用lambda表达式仅引用方法时,都可以将lambda表达式替换为方法引用。在本教程中,我们将详细解释方法参考概念。 2 Java8 方法引用

  • 问题内容: 我理解这段代码: ..静态方法“隐藏”了声明的静态方法,而不是在多态性意义上覆盖它。 …将输出: 在Foo 中 重新定义为in 将禁用隐藏它的功能,并且重新运行将输出: 在Foo 中 ( 编辑 :将方法标记为时,编译失败,并且仅在我删除后再次运行) 如果将静态方法声明为,是否会阻止子类有意或无意地重新定义该方法,这是否被视为不好的做法? (这很好地说明了使用行为是..) 问题答案: 我