前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中的lambda每次执行都会创建一个新对象吗

Java中的lambda每次执行都会创建一个新对象吗

作者头像
KINGYT
发布2020-01-13 17:26:54
5.8K6
发布2020-01-13 17:26:54
举报

之前写过一篇文章 Java中的Lambda是如何实现的,该篇文章中讲到,在lambda表达式执行时,jvm会先为该lambda生成一个java类,然后再创建一个该类对应的对象,最后执行该对象对应的方法,以此来执行我们写的lambda方法体。

那该lambda表达式每次执行时都会创建一个新对象吗?

我们先卖个关子,不说答案,先看几个例子:

$ cat Test.java
import java.util.function.Consumer;

public class Test {
  public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
      test(i);
    }
  }

  static void test(int a) {
    forEach(b -> check(a + b));
  }

  static void forEach(Consumer<Integer> c) {
    System.out.println(c);
    c.accept(1);
  }

  static void check(int i) {
    if (i < 0) {
      throw new RuntimeException();
    }
  }
}

$ java Test.java
Test$$Lambda$216/0x0000000800c93c40@f0f2775
Test$$Lambda$216/0x0000000800c93c40@5a4aa2f2
Test$$Lambda$216/0x0000000800c93c40@6591f517

由上可见,我们在调用forEach方法时,传入的参数是一个lambda表达式,forEach方法在执行前,会输出一下这个lambda表达式对应的对象。

通过上面的输出结果我们发现,三次输出的lambda表达式对应的对象的值均不同,由此可知,每次调用forEach方法时,都新建了一个该lambda表达式对应的对象。

我们再来看另外一个例子:

$ cat Test.java
import java.util.function.Consumer;

public class Test {
  public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
      test(i);
    }
  }

  static void test(int a) {
    forEach(Test::check); // 等同于forEach(b -> check(b))
  }

  static void forEach(Consumer<Integer> c) {
    System.out.println(c);
    c.accept(1);
  }

  static void check(int i) {
    if (i < 0) {
      throw new RuntimeException();
    }
  }
}

$ java Test.java
Test$$Lambda$221/0x0000000800c94040@709ba3fb
Test$$Lambda$221/0x0000000800c94040@709ba3fb
Test$$Lambda$221/0x0000000800c94040@709ba3fb

这次这个例子和上个例子的区别是,传入forEach方法的lambda表达式里,没有再使用test方法的参数a,执行该示例后我们发现,三次输出的lambda表达式的对象结果都是一样的,这说明三次forEach执行使用都是同一个lambda对象。

也就是说,如果lambda表达式里使用了上下文中的其他变量,则每次lambda表达式的执行,都会创建一个新对象,而如果lambda表达式里没有使用上下文中的其他变量,则每次lambda的执行,都共用同一个对象,对吗?

在初次执行上面的两个示例后,看到执行结果,我就是这么猜测的,而在又一遍看过jvm中lambda相关实现代码后,也验证了我这个猜测是对的。

关键代码是下面这个方法:

// java.lang.invoke.InnerClassLambdaMetafactory
CallSite buildCallSite() throws LambdaConversionException {
    final Class<?> innerClass = spinInnerClass();
    if (invokedType.parameterCount() == 0 && !disableEagerInitialization) {
        // In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance,
        // unless we've suppressed eager initialization
        final Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
        Object inst = ctrs[0].newInstance();
        return new ConstantCallSite(MethodHandles.constant(samBase, inst));
    } else {
        return new ConstantCallSite(
                    MethodHandles.Lookup.IMPL_LOOKUP
                         .findStatic(innerClass, NAME_FACTORY, invokedType));
    }
}

为了方便理解,我对该方法做了精简。

在该方法中,先调用spinInnerClass方法,为该lambda表达式生成一个java类,然后判断该lambda表达式有没有使用上下文中的其他变量,如果没有(invokedType.parameterCount() == 0),则直接创建一个该类的实例,并在以后每次执行该lambda表达式时,都使用这个实例。

如果使用了上下文中的其他变量,则每次执行lambda表达式时,都会调用innerClass里的一个名为NAME_FACTORY(get$Lambda)的静态方法,该方法会新建一个新的lambda实例。该过程在上一篇文章中有讲,这里就不再赘述了。

综上可知:

当lambda表达式里没有使用上下文中的其他变量时,则每次执行lambda表达式都使用同一个对象。

当lambda表达式里使用了上下文中的其他变量时,则每次执行lambda表达式都会新建一个对象。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档