3

Vaguely speaking, in Java, anonymous classes provide some kind of closure: it is possible to access (final) variables from inside an anonymous class. In the following, I call this kind of variables "closure variables".

The anonymous class's constructor signature differs whether such variables are constant or non-constant.

Is there an elegant way to access non-constant "closure variables" reflectively?

Here is my test class that illustrates this challenge:

package com.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import org.junit.Test;

public class ReflectiveInstantiationAnonymousClassTest {

    @Test
    public void testInstantiateConstantAnonymousClassReflectively() {
        System.out
                .println("About to define anonymous class with constant closure");
        final String constantString = "constant String";
        System.out.println("constantString is " + constantString);

        Class<? extends Object> clazz = new Object() {
            public String toString() {
                return "Hello with " + constantString;
            }
        }.getClass();

        Object anonymousClassInstance1 = instantiateAnonymousClass(clazz);
        Object anonymousClassInstance2 = instantiateAnonymousClass(clazz);
        System.out.println(anonymousClassInstance1);
        System.out.println(anonymousClassInstance2);
    }


    @Test
    public void testInstantiateNonConstantAnonymousClassReflectively() {
        System.out
                .println("About to define anonymous class with variable closure");
        final String variableString = String.valueOf( System.currentTimeMillis() );
        System.out.println("variableString is " + variableString);

        Class<? extends Object> clazz = new Object() {
            public String toString() {
                return "Hello with " + variableString;
            }
        }.getClass();

        Object anonymousClassInstance1 = instantiateAnonymousClass(clazz);
        Object anonymousClassInstance2 = instantiateAnonymousClass(clazz);
        System.out.println(anonymousClassInstance1);
        System.out.println(anonymousClassInstance2);
    }

    @SuppressWarnings("unchecked")
    private <T> T instantiateAnonymousClass(Class<T> clazz) {
        T instance = null;
        Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
        System.out.println("-+-" + allConstructors.length + " constructor(s) defined by class " + clazz.getName() );
        for(Constructor<?> constructor : allConstructors) {
            System.out.println(" +- a constructor  with " + constructor.getParameterTypes().length + " parameter(s): " + constructor.toGenericString() );
        }
        System.out.println();
        System.out.println("Instantiating anonymous class");
        try {
            instance = (T) clazz.getDeclaredConstructors()[0].newInstance(this);
            //                                                                .
            //                                                               /|\
            //    how can I provide a variable closure variable there?--------+

        } catch (InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return instance;
    }
}

Test case testInstantiateConstantAnonymousClassReflectively works well and prints out:

About to define anonymous class with constant closure
constantString is constant String
-+-1 constructor(s) defined by class com.example.ReflectiveInstantiationAnonymousClassTest$1
 +- a constructor  with 1 parameter(s): com.example.ReflectiveInstantiationAnonymousClassTest$1(com.example.ReflectiveInstantiationAnonymousClassTest)

Instantiating anonymous class
-+-1 constructor(s) defined by class com.example.ReflectiveInstantiationAnonymousClassTest$1
 +- a constructor  with 1 parameter(s): com.example.ReflectiveInstantiationAnonymousClassTest$1(com.example.ReflectiveInstantiationAnonymousClassTest)

Instantiating anonymous class
Hello with constant String
Hello with constant String

But test case testInstantiateNonConstantAnonymousClassReflectively prints out the following:

About to define anonymous class with variable closure
variableString is 1371946280882
-+-1 constructor(s) defined by class com.example.ReflectiveInstantiationAnonymousClassTest$2
 +- a constructor  with 2 parameter(s): com.example.ReflectiveInstantiationAnonymousClassTest$2(com.example.ReflectiveInstantiationAnonymousClassTest,java.lang.String)

Instantiating anonymous class
java.lang.IllegalArgumentException: wrong number of arguments
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    at com.example.ReflectiveInstantiationAnonymousClassTest.instantiateAnonymousClass(ReflectiveInstantiationAnonymousClassTest.java:60)
    at com.example.ReflectiveInstantiationAnonymousClassTest.testInstantiateNonConstantAnonymousClassReflectively(ReflectiveInstantiationAnonymousClassTest.java:43)

Notice that for test case testInstantiateNonConstantAnonymousClassReflectively, a different kind of constructor is generated. This constructor requires an additional String parameter, as variableString cannot be "baked in" and can only be determined during runtime.

Community
  • 1
  • 1
Abdull
  • 26,371
  • 26
  • 130
  • 172
  • 1
    +1 For a well written question, but I don't think what you're asking for is possible. If variables aren't baked in, then they're not baked in. Using reflection, it falls on the caller to provide the constructor arguments - it's the same situation with dynamic invocation of inner classes' constructors requiring the outer instance. – Paul Bellora Jun 23 '13 at 01:16

2 Answers2

1

I don't think what you're asking for is possible. If variables aren't baked into the anonymous class, then they're not baked in. Using reflection, it falls on the caller to provide all the constructor arguments, including synthetic ones.

It's the same situation with dynamic invocation of inner classes' constructors requiring the outer instance. I notice instantiateAnonymousClass relies on the fact the specified anonymous class is declared in the same class - that allows it to simply pass in this. How would it handle anonymous classes declared elsewhere? You mention this is meant to be a library method, so it's an important question to consider. Your method could look up their outer classes and instantiate them too, but what if their constructors required further arguments? Ultimately it falls on the caller to provide whatever's needed for dynamic invocation.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
0
newInstance(this, variableString);
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • `instantiateAnonymousClass(..)` is supposed to be a library function. `variableString` is outside this library's scope. – Abdull Jun 23 '13 at 00:48
  • 1
    @Abdull - then the caller into the library will need to provide the arguments, just like any other constructor paramter. – jtahlborn Jun 23 '13 at 03:30
  • Do *non-constant "closure variables"* cause a deterministic order of parameter types in the generated constructor? If so, one can create a correctly ordered `List nonConstantClosureVariables`, provide it to `instantiateAnonymousClass(clazz, nonConstantClosureVariables)`, then inside the library method call `newInstance(this, nonConstantClosureVariables)` ... – Abdull Jun 23 '13 at 11:33