218

When I iterate over a collection using the new syntactic sugar of Java 8, such as

myStream.forEach(item -> {
  // do something useful
});

Isn't this equivalent to the 'old syntax' snippet below?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

Does this mean a new anonymous Consumer object is created on the heap every time I iterate over a collection? How much heap space does this take? What performance implications does it have? Does it mean I should rather use the old style for loops when iterating over large multi-level data structures?

Lii
  • 11,553
  • 8
  • 64
  • 88
Bastian Voigt
  • 5,311
  • 6
  • 47
  • 65
  • 70
    Short answer: no. For stateless lambdas (those that do not capture anything from their lexical context), only one instance will ever be created (lazily), and cached at the capture site. (This is how the implementation works; the spec was carefully written to allow, but not require, this approach.) – Brian Goetz Dec 17 '14 at 16:23
  • 8
    http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood – Broken_Window Dec 17 '14 at 16:28

3 Answers3

195

It is equivalent but not identical. Simply said, if a lambda expression does not capture values, it will be a singleton that is re-used on every invocation.

The behavior is not exactly specified. The JVM is given big freedom on how to implement it. Currently, Oracle’s JVM creates (at least) one instance per lambda expression (i.e. doesn’t share instance between different identical expressions) but creates singletons for all expressions which don’t capture values.

You may read this answer for more details. There, I not only gave a more detailed description but also testing code to observe the current behavior.


This is covered by The Java® Language Specification, chapter “15.27.4. Run-time Evaluation of Lambda Expressions

Summarized:

These rules are meant to offer flexibility to implementations of the Java programming language, in that:

  • A new object need not be allocated on every evaluation.

  • Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example).

  • Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example).

  • If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • can you please explain this sentence in your answer " if a lambda expression does not capture values" I am bit confused – H.S Jul 21 '23 at 10:30
  • 2
    @H.S similar to inner classes. Accessing local variables of the surrounding context will capture their value and use that value when the lambda expression’s body is evaluated. Accessing instance fields of the surrounding object requires capturing the value of the `this` reference, to be able to read the field when the lambda expression’s body is evaluated. You may follow the link given in the answer ([this one](https://stackoverflow.com/a/23991339/2711488)) for practical examples. – Holger Jul 21 '23 at 10:52
28

When an instance representing the lambda is created sensitively depends on the exact contents of your lambda's body. Namely, the key factor is what the lambda captures from the lexical environment. If it doesn't capture any state which is variable from creation to creation, then an instance will not be created each time the for-each loop is entered. Instead a synthetic method will be generated at compile time and the lambda use site will just receive a singleton object that delegates to that method.

Further note that this aspect is implementation-dependent and you can expect future refinements and advancements on HotSpot towards greater efficiency. There are general plans to e.g. make a lightweight object without a full corresponding class, which has just enough information to forward to a single method.

Here is a good, accessible in-depth article on the topic:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
0

You are passing a new instance to the forEach method. Every time you do that you create a new object but not one for every loop iteration. Iteration is done inside forEach method using the same 'callback' object instance until it is done with the loop.

So the memory used by the loop does not depend on the size of the collection.

Isn't this equivalent to the 'old syntax' snippet?

Yes. It has slight differences at a very low level but I don't think you should care about them. Lamba expressions use the invokedynamic feature instead of anonymous classes.

aalku
  • 2,860
  • 2
  • 23
  • 44
  • Do you have any documentation specifying this? It's quite the interesting optimization. – A. Rama Dec 17 '14 at 11:26
  • Thanks, but what if I have a collection of collections of collections, for example when doing a depth-first search on a tree datastructure? – Bastian Voigt Dec 17 '14 at 11:27
  • 3
    @A.Rama Sorry, I don't see the optimization. It is the same with or without lambdas and with or without forEach loop. – aalku Dec 17 '14 at 11:33
  • 1
    It's not really the same, but still at most one object per nesting level will be needed at any one time, which is negligible. Each new iteration of an inner loop will create a new object, most probably capturing the current item from the outer loop. This creates some GC pressure, but still nothing to really worry about. – Marko Topolnik Dec 17 '14 at 11:35
  • 7
    @aalku: "*Every time you do that you create a new object*": Not according to to [this answer by Holger](http://stackoverflow.com/a/27524543/452775) and [this comment by Brian Goetz](https://stackoverflow.com/questions/27524445/does-a-lambda-expression-create-an-object-on-the-heap-every-time-its-executed#comment43488801_27524445). – Lii Mar 06 '16 at 11:14
  • @lii you are right but not always. If the lambda capture a value it is not a singleton and that all is JVM dependant. – aalku Mar 06 '16 at 18:42