Invoking a method by reflection

Asked

Viewed 233 times

2

I took a look at that question You can pass methods as a parameter? and tried to invoke a method from one class, in another, passing as parameters the screen/class and the method name.

The purpose of doing this, is that in my application, I do several routines within a given method, which is triggered by an event (a ActionListener, for example) and every time I need to do something "different", I have to overwrite the event that triggers this method, put the new routine and sometimes even repeat others.

I believe that by doing so, I could have a greater possibility in doing some routines. I could have several different screens with the component MyField which does the same function, and on the screens I want a particular method to be executed together or after the methods of MyField, I would do it quietly, passing it as parameter.

Apparently, it seemed to me a coherent idea, but I could be mistaken, and in this case, I’m open to more efficient and "correct suggestions".

To illustrate better what I intend, I made a simple example.

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TesteReflection extends JFrame {

    public static void main(String[] args) {
        Runnable startApp = () -> {
            TesteReflection tr = new TesteReflection();
        };
        SwingUtilities.invokeLater(startApp);
    }

    private MyField myField = new MyField();
    private JButton button = new JButton("Click !");

    public TesteReflection() {
        add(button);
        setSize(300, 200);
        setVisible(true);
        button();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void button() {
        button.addActionListener(e -> {
            metodoA();
        });
    }

    private void metodoA() {
        MyField.actions(myField, TesteReflection.class, "metodoB");
    }

    private void metodoB() {
        System.out.println("Método B");
        //faz alguma coisa ..
    }
}
import java.lang.reflect.Method;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MyField extends JPanel {

    private JTextField jTextField = new JTextField();

    public MyField() {
        actions();
    }

    private void actions() {
        jTextField.addActionListener(e -> {
            //faz alguma coisa ..
        });
    }

    public static void actions(MyField component, Class tela, String methodName) {
        component.getjTextField().addActionListener(e -> {
            try {
                //Method method = tela.getDeclaredMethod(methodName, new Class[]{});
                Method method = tela.getClass().getDeclaredMethod(methodName, String.class);
                method.setAccessible(true);
                method.invoke(tela);

                System.out.println("Chamei com sucesso o método " + methodName);
            } catch (Exception ev) {
                ev.printStackTrace();
            }
        });
    }

    public JTextField getjTextField() {
        return jTextField;
    }

}

1 answer

2


Your Reflection fails because the metodoB is an instance method and not a static method. Therefore, to invoke it (either normally or by Reflection), you need the instance.

You even try to solve it using tela.getClass(). However, tela is the type Class, and so the result of that would be Class.class, which is definitely not what you want. Also, when specifying String.class as parameter, you will not find what you wanted, because the method you want is just metodoB() and not metodoB(String).

The code can be like this:

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TesteReflection extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new TesteReflection();
        });
    }

    private MyField myField = new MyField();
    private JButton button = new JButton("Click !");

    public TesteReflection() {
        add(button);
        setSize(300, 200);
        setVisible(true);
        button();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void button() {
        button.addActionListener(e -> metodoA());
    }

    private void metodoA() {
        myField.actions(this, TesteReflection.class, "metodoB");
    }

    private void metodoB() {
        System.out.println("Método B");
        //faz alguma coisa ..
    }
}
import java.lang.reflect.Method;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MyField extends JPanel {

    private JTextField jTextField = new JTextField();

    public MyField() {
    }

    public <E> void actions(E tela, Class<E> classe, String methodName) {
        jTextField.addActionListener(e -> {
            try {
                Method method = classe.getDeclaredMethod(methodName);
                method.setAccessible(true);
                method.invoke(tela);

                System.out.println("Chamei com sucesso o método " + methodName);
            } catch (Exception ev) {
                ev.printStackTrace();
            }
        });
    }
}

However, what you want would probably look better and easier with method References:

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TesteReflection extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new TesteReflection();
        });
    }

    private MyField myField = new MyField();
    private JButton button = new JButton("Click !");

    public TesteReflection() {
        add(button);
        setSize(300, 200);
        setVisible(true);
        button();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void button() {
        button.addActionListener(e -> metodoA());
    }

    private void metodoA() {
        myField.actions(this::metodoB);
    }

    private void metodoB() {
        System.out.println("Método B");
        //faz alguma coisa ..
    }
}
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MyField extends JPanel {

    private JTextField jTextField = new JTextField();

    public MyField() {
    }

    public void actions(Runnable r) {
        jTextField.addActionListener(e -> r.run());
    }
}
  • What the :: does ? It’s some kind of "test" ?

  • 1

    @Gustavosantos Is the method Reference (reference to method). In this case the this::metodoB means "the method metodoB of the object this". The compiler strives to find out which functional interface (an interface that only has a single abstract method) is compatible with the reference and context in which it is used. In this case, it is compatible with Runnable, for the return is void, the metodoB does not receive parameters and it is being used as a parameter to a call where the type is Runnable. [continues...]

  • 1

    [...continuation] With this, it’s more or less like the compiler replaces this::metodoB for new Runnable() { public void run() { metodoB(); }}.

  • understood ! And not wanting to abuse your help, more, what happens when the method has parameters, or some kind of return ?

  • 1

    @Gustavosantos The compiler looks at which functional interfaces can be used in context and which are the possible methods the program refers to (as they can be overloaded, there may be more than one). Then it checks which of the possible methods is compatible with which possible functional interface and gives error if there is more than one possibility or if there is none. Remember that the functional interface method can have different returns, different types of parameters and different exceptions declared, all this is taken into account to check whether it is compatible or not.

  • when a method has parameters, I can’t use method Reference, or even Reflection. How would that be possible? Java has a method, implementation or something else, which allows me to invoke a method with parameters ?

  • 1

    @Gustavosantos Yes, you get it in both cases, both with method References and with Reflection. Let’s assume that metodoB had a type parameter JLabel and return a String. In that case, if the method actions receive Function<JLabel, String>, compiler would see that this would be the compatible functional interface. In the case of Reflection, you would use classe.getDeclaredMethod(methodName, JLabel.class); and method.invoke(tela, algumaJLabel);. The call to invoke returns Object, in the case of what is returned by the method invoked by Reflection.

  • Function expects two values, correct ? However, if the method is void and passes only parameter, what is used ?

  • 1

    @Gustavosantos Não. Function<A, B> has a type parameter A and a comeback of the kind B. In the case of void with a parameter X, use Consumer<X>. Take a look at the package interfaces java.util.function that there is that most of them are defined (with the exception of Runnable). If you’re in a case where none of the existing functional interfaces serve, invent yours: just declare an interface with a single abstract method and if your method has a compatible format, the compiler accepts the method Reference.

  • Very good, I’ll give a deal for sure. Thank you !

Show 5 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.