Invokedynamic with non-static context

Tags: , , ,



I learnt that invokedynamic instruction of bytecode calls a static method representation of lambda.

Please let me know if that is incorrect.

if correct, then how is below code working?

String[] arr = new String[1];
Stream.of(arr).forEach((s) -> System.out.println(this));

Answer

It’s not correct to say that lambda expressions were always compiled to a static method. It’s not specified, how they are compiled, which leaves room for two different strategies for a lambda expression that captures this, like your s -> System.out.println(this).

  1. use an instance method:

    private void compiler$chosen$name(String s) {
        System.out.println(this);
    }
    
  2. use a static method:

    private static void compiler$chosen$name(TypeOfThis var0, String s) {
        System.out.println(var0);
    }
    

Both methods work equally well when the invokedynamic instruction points to a bootstrap method in the LambdaMetafactory. In either case, the invokedynamic instruction will have a signature consuming a TypeOfThis instance and producing a Consumer<String>. From the documentation of the LambdaMetafactory, you can derive that it will treat the receiver of a non-static target method like an implied the first argument, which makes the functional signature of both variants identical. All that matters, is that the argument to the consumer’s accept method has to correspond to the last argument of the list.

I’ve encountered both strategies in practice, so this is indeed compiler dependent.

Note that these strategies also work on source code level, when using method references:

public class Example {
    BiConsumer<Example,String> variant1 = Example::instanceMethod;
    BiConsumer<Example,String> variant2 = Example::staticMethod;

    private void instanceMethod(String s) {
        System.out.println(this);
    }

    private static void staticMethod(Example instance, String s) {
        System.out.println(instance);
    }
}

This demonstrates the equivalence of a method receiver and the the first argument to a static method. However, when it comes to binding an argument, only Consumer<String> c = this::instanceMethod; works with method references. The other binding features of the LambdaMetafactory are only used by compiler generated code for lambda expressions.



Source: stackoverflow