1

This code runs fine in java 11.

package test;

import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.function.Consumer;

public class LambdaArg1ReflectionTest {
    @Test
    public void test() throws IllegalAccessException {
        // Create a lambda with reference to parent class (this)
        Consumer<String> lambda = s -> this.parseInt(s, 7);

        // Get the value from the lambdas "this" reference
        Field lambda_arg1_field = lambda.getClass().getDeclaredFields()[0];
        lambda_arg1_field.setAccessible(true);
        Object lambda_arg1_value = lambda_arg1_field.get(lambda);
        // Assert that the value is as we expected
        Assert.assertEquals(this, lambda_arg1_value);

        // Attempt to assign the same value
        lambda_arg1_field.set(lambda, lambda_arg1_value);
    }

    private int parseInt(String s, int i) {
        return Integer.parseInt(s + i);
    }
}

But, in java 17 it throws an exception:

    java.lang.IllegalAccessException: Can not set final test.LambdaArg1ReflectionTest field test.LambdaArg1ReflectionTest$$Lambda$40/0x0000000800c191a8.arg$1 to test.LambdaArg1ReflectionTest

    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79)
    at java.base/java.lang.reflect.Field.set(Field.java:799)
    at test.LambdaArg1ReflectionTest.test(LambdaArg1ReflectionTest.java:23)
  • Why?
  • Is it not supposed to work in java 17?
  • Is there any way to work around this?

Obviously this is an example. What the real code attempts to do is to replace the lambdas this reference with a spy object, invoke the lambda and then capture the arguments given to this.parseInt. Ultimately what it does is to serialize the first method invocation of a lambda.

tsoiland
  • 101
  • 7
  • 4
    `lambda`'s class is a [hidden class](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/Class.html#isHidden()) and the [documentation](https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/reflect/Field.html#set(java.lang.Object,java.lang.Object)) of `set()` states: "If the underlying field is final, this Field object has write access if and only if the following conditions are met: ... the field's declaring class is not a hidden class ... If any of the above checks is not met, this method throws an IllegalAccessException" – user16320675 Nov 22 '22 at 16:18
  • 10
    It is not "supposed" to work in Java 11, either; you just managed to get away with it by luck, and your luck ran out. What reason do you even have to believe that the first field returned by `getDeclaredFields` corresponds to any captured value, let alone the one you think, let alone to think that by rewriting it you can predictably change the behavior of the lambda? You relied heavily on questionable assumptions about the implementation details of the translation scheme. This is exactly what you're not supposed to do, and this is why. – Brian Goetz Nov 22 '22 at 19:54
  • 3
    The reasoning about this feature is similar to what has been said in [this answer](https://stackoverflow.com/a/71051688/2711488) about `record` fields. Serialization doesn’t store the lambda’s internal representation and cloning isn’t supported, so there is no reason to support overwriting `final` fields. – Holger Nov 23 '22 at 11:47

1 Answers1

0

Short answer : no you can't doing that this way

Explanation : I don't test if on previsous version of java this can work but in j17 i am sur you can't : the lambda you use is basicaly something close of an innerClass and your are trying to edit the class in which this as been declare and that reference done by using a class pattern that is in the jvm. by the way the message is quite explicite the reference is a final one so no set will be allowed.

may be a solution will be to change le lambda as a Biconsumer

BiConsumer<LambdaArg1ReflectionTest,String> lambda = (o,s) -> o.parseInt(s, 7);

this way you can specify the object on which apply your fonction including a stub.