通过反射在匿名类实例中访问任何类型的“闭包变量”

3
粗略地说,在Java中,匿名类提供了一种闭包:可以从匿名类内部访问(final)变量。在下文中,我将这种变量称为“闭包变量”。
根据此处的解释,匿名类的构造函数签名会因这些变量是常量还是非常量而有所不同。
是否有一种优雅的方式可以反射性地访问非常量“闭包变量”?
以下是展示这个挑战的测试类:
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;
    }
}

测试用例testInstantiateConstantAnonymousClassReflectively执行良好,并输出以下内容:

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

但是测试用例testInstantiateNonConstantAnonymousClassReflectively打印出以下内容:

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)

请注意,对于测试用例 testInstantiateNonConstantAnonymousClassReflectively ,将生成一种不同类型的构造函数。此构造函数需要一个额外的String参数,因为variableString无法“烘焙”,只能在运行时确定。

1
对于一个写得很好的问题,我给你点赞,但我认为你所要求的是不可能的。如果变量没有被固定下来,那么它们就没有被固定下来。使用反射,调用者需要提供构造函数参数 - 这与动态调用内部类构造函数需要外部实例的情况相同。 - Paul Bellora
2个回答

1
我认为你所要求的是不可能的。如果变量没有嵌入匿名类中,那么它们就没有嵌入。使用反射,需要调用者提供所有构造函数参数,包括合成参数。
动态调用内部类构造函数需要外部实例也是同样的情况。我注意到instantiateAnonymousClass依赖于指定的匿名类在同一个类中声明的事实 - 这使得它可以简单地传递this。如果匿名类在其他地方声明,它将如何处理?你提到这是一个库方法,所以考虑这个问题是很重要的。你的方法可以查找它们的外部类并实例化它们,但如果它们的构造函数需要更多的参数呢?最终,需要调用者提供动态调用所需的任何内容。

0
newInstance(this, variableString);

instantiateAnonymousClass(..) 应该是一个库函数。variableString 不在此库的范围内。 - Abdull
1
@Abdull - 然后调用库的调用者将需要提供参数,就像任何其他构造函数参数一样。 - jtahlborn
非常量的“闭包变量”是否会导致生成的构造函数中参数类型的确定顺序?如果是这样,那么可以创建一个正确排序的List<Object> nonConstantClosureVariables,将其提供给instantiateAnonymousClass(clazz, nonConstantClosureVariables),然后在库方法内部调用newInstance(this, nonConstantClosureVariables)... - Abdull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接