简体   繁体   中英

Is it possible to pass a non-final variable to a method of an anonymous class?

I need to send code to another method, as the number of methods in the class may change, I use reflection. But the problem is that I can not enumerate them in a loop benchmarkMethods.get(i).invoke(set, null); , because it is possible to me anonymous class can only pass final variable. What do I do in this case?

public void RunAllBenchmarks(final Object set, boolean randomOrder) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    final List<Method> benchmarkMethods = new ArrayList<Method >();
    final Class benchClass = set.getClass();
    Method[] methods = benchClass.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++){
        Annotation[] annotations = methods[i].getAnnotations();
        if (annotations.length != 0){
            for (int j = 0; j < annotations.length; j++)
            if (annotations[j].annotationType().toString().contentEquals("interface Benchmark"))
                benchmarkMethods.add(methods[i]);
        }
    }
    for (int i = 0; i < benchmarkMethods.size(); i++){
        String name = null;
        Method method = benchmarkMethods.listIterator().next();
        MeasureAndRecord(name, new IFunc() {
            @Override
            public Object onEvent() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {

                return benchmarkMethods.get(i).invoke(set, null);
            }
        });

    }

    PrintWriter writer = new PrintWriter(System.out);
    PrintResults(writer);
}

I thought I would add, there is a trick I use to pass non-final variables into an anonymous class. Taking a section of the code you wrote above, I've made some small changes to show how I do it.

I've assumed here measureAndRecord is a method (I've decapitalised the first character to signify that) and that IFunc is the class that you are creating an anonymous extension of. I've also assumed that Method method ... is supposed to be the method you want to pass to your anonymous class (because in your code it doesn't do anything).

The trick here is to add a new method called init which you can pass your variables to (final or non-final) and which returns this (which in this case is the anonymous class). What happens is the anonymous class is called, the init method is immediately called, and then the object (which is an IFunc as required) is returned and used.

You must call the init method upon construction (before assignment) and you cannot chain methods together in this way. The reason is once the object is returned from the first method call, it is an IFunc, not the anonymous subclass that you created, and so Java will no longer recognise anything but the overriden methods after that.

Anyway, have a play. I find it very handy, if a bit of a cheat. Even when Java 8 comes along and allows virtually final variables to be passed, this will still work with the variable variables.

...

for (int i = 0; i < benchmarkMethods.size(); i++) {
    String name = null;
    Method method = benchmarkMethods.get(i); // Assumption on my part

    measureAndRecord(name, new IFunc() {
        // Anonymous class variables matching what we want to pass
        Method method;
        int i;

        // Cheating "Constructor"
        public IFunc init (Method method, int i) {
            this.method = method;
            this.i = i;
            return this; // Must return this for assignment
        }

        @Override
        public Object onEvent() throws InvocationTargetException,    
            IllegalAccessException, NoSuchMethodException {
                return method.invoke(set, null);
            }
        }.init(method, i)); // Note that init is called BEFORE assignment
    }
}

...

An approach could be to avoid the anonymous inner class. ie have a class MethodIFunc implements IFunc that invokes the method on its onEvent() ie

for (...) {
    Method method = ...

    // this class calls method.invoke(..) inside its onEvent() method
    MethodIFunc mi = new MethodIFunc(method, set);
    MeasureAndRecord(name, mi);
}

Another approach (btw: you have a double iteration in the second for-loop I assume this is not the actual code) could be to use the for-each statement that allows the variable to be declared final:

for (final Method method : benchmarkMethods) {
    ...
    MeasureAndRecord(name, new IFunc() {
        @Override
        public Object onEvent() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
            return method.invoke(set, (Object[]) null);
        }
    });

}

You can use a mutable int wrapper, eg

final AtomicInteger ai = new AtomicInteger();
for (int i = 0; i < benchmarkMethods.size(); i++){
    String name = null;
    Method method = benchmarkMethods.listIterator().next();
    ai.set(i);
    MeasureAndRecord(name, new IFunc() {
        @Override
        public Object onEvent() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
            return benchmarkMethods.get(ai.get()).invoke(set, null);
        }
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM