Watch out “this”, and you won’t get lost

I have gone through a painful afternoon just because I mess up with “this”. Let me tell you what has happened.

I’ll put it in the simplest form: I have a class “Invoker”, it simply invoke all methods of a object.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class Invoker {
    public void invoke(Object obj) {
        Method[] methods = obj.getClass().getDeclaredMethods();
        for(Method m: methods){
            try {
                m.invoke(obj);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

and here is ClassA which has two methods just print their names.

public class ClassA {
    public void m1(){
        System.out.println("m1");
    }
    
    public void m2(){
        System.out.println("m2");
    }
    
    public void run(Invoker invoker){
        invoker.invoke(this);
    }
}

With these two classes, I could at least prove I am not doing anything wrong by run this line:

new ClassA().run(new Invoker());
 
outputs:
m1
m2

OK, it works. Now there comes ClassB, it want do something different in method m1 while still keep using the implementation of ClassA.m2

public class ClassB extends ClassA {
    public void m1(){
        System.out.println("m1_sub");
    }
}

Let’s run ClassB

new ClassB().run(new Invoker());
 
output:
m1_sub

Ooops, where is the output of method m2? why m2 is not working anymore? Isn’t ClassB.m2 inherited the ClassA.m2?  Why m2 stop working while run is still working?

Yes, ClassB is inherit from ClassA, that’s the reason why we could still get some output because run method of ClassB is still working. Remember we are not directly calling m2, but by reflection. Let’s look at the run method:

public void run(Invoker invoker){
    invoker.invoke(this);
}

The run method takes in a instance of Invoker and run it with “this”. So when we call run on ClassB, the Invoker.invoke method call with a instance of ClassB, and because this is no m2 defined on ClassB, there is actually no m2 method call in the call of of Invoker.invoke.

Phew~

But knowing m2 won’t get things any better, we still need to reuse the default implementations of ClassA, what should we do? It won’t be a problem if I have made myself understood: Implement the m2 method and call its super class’s method. So our ClassB finally looks like this:

public class ClassB extends ClassA {
    public void m1(){
        System.out.println("m1_sub");
    }
    
    public void m2(){
        super.m2();
    }
}

To summary, take care of “this” when subclassing a Class involved in reflection.

Advertisements