4

As far as I know, when you define a method in a function, an object is instantiated:

myList.stream().map(x->x.getName().replaceAll('a','b')).toList();

Or the equivalent

 Function<MyObject,String> myFunc = x -> {return x.getName().replaceAll('a','b');}
 myList.stream().map(myFunc).toList();

x->x.getName().replaceAll('a','b') is created as a functional interface object (and requires memory allocation, a new somewhere/somehow, right?).

However, if I pass an already existing method as a parameter, is anything created?

class A{
  public list<String> transform(List<String> input){
    return input.stream().filter(this::myFilter).filter(A::staticFilter).toList();
  }
  public boolean myFilter(String s){ // Whatever }
  public static boolean staticFilter(String s) { // whatever }
}

What happens here:

  • Is myFilter "wrapped" in a functional interface? (is it the same for a static method reference?)
  • Is there something specific that happens at bytecode level which is not "clear" on language level (like method pointer or something?).
Asoub
  • 2,273
  • 1
  • 20
  • 33
  • The answer is "it depends": the spec is intentionally vague as to when/where/by whom an object is created there, how aggressively it might be cached/shared or if a real object is necessary at all to allow the JVM to optimize as it sees fit. It only guarantees that *an object* exists when you pass it, but not where/when it will be created. – Joachim Sauer Oct 12 '22 at 12:36
  • 1
    Do you mean `filter(A::staticFilter)` instead of `filter(A.staticFilter)`? – Alexander Ivanchenko Oct 12 '22 at 12:47

2 Answers2

3

Well, the compiler has a lot of leeway in how it actually implements the code you write, but generally .map() takes a Function Object so whatever expression you put in the parentheses will produce an object.

That does not mean, however, that a new Object is created every time. In your lambda example, the lambda function doesn't reference anything defined in an enclosing method scope, so a single Function object can be created and reused for all calls.

Similarly, the A::staticFilter reference only needs to produce one Function.

The object created by this::myFilter, however, needs to have a reference to this (unless the compiler can determine that it doesn't!), and so you will certainly get a new Function created inside each call to transform.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
3

From JavaDoc Api

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

As to if the lambda expression will create an instance in heap or not, you can follow this thread where the top comment from @Brian Goetz might be helpful.

About lambda expressions:

Also as indicated here in Java Specifications for Run-Time Evaluation of Lambda Expressions

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).

So to your question:

x->x.getName().replaceAll('a','b') is created as a functional interface object (and requires memory allocation, a new somewhere/somehow, right?).

The answer is some times yes, some times no. Not always the same case.

About method reference expressions:

Evaluation of a method reference expression produces an instance of a functional interface type (§9.8). Method reference evaluation does not cause the execution of the corresponding method; instead, this may occur at a later time when an appropriate method of the functional interface is invoked.

Based on what is written here for Run-Time Evaluation of Method References

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

I would assume that a functional interface type is created but not each time with each invocation. It should as well be cached and optimized for the less amount of evaluations.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47