Como invoco um método Java quando recebo o nome do método como uma string?

684

Se eu tiver duas variáveis:

Object obj;
String methodName = "getName";

Sem conhecer a classe de obj, como posso chamar o método identificado por methodNameela?

O método que está sendo chamado não possui parâmetros e um Stringvalor de retorno. É um getter para um bean Java .

brasskazoo
fonte
3
Ou usar o reflexo api ou utilização Groovy
Peter Kelley

Respostas:

972

Codificação a partir do quadril, seria algo como:

java.lang.reflect.Method method;
try {
  method = obj.getClass().getMethod(methodName, param1.class, param2.class, ..);
} catch (SecurityException e) { ... }
  catch (NoSuchMethodException e) { ... }

Os parâmetros identificam o método muito específico que você precisa (se houver vários sobrecarregados disponíveis, se o método não tiver argumentos, apenas forneça methodName).

Então você invoca esse método chamando

try {
  method.invoke(obj, arg1, arg2,...);
} catch (IllegalArgumentException e) { ... }
  catch (IllegalAccessException e) { ... }
  catch (InvocationTargetException e) { ... }

Novamente, deixe de fora os argumentos .invoke, se você não tiver nenhum. Mas sim. Leia sobre Java Reflection

Henrik Paul
fonte
2
Fiquei um pouco chateado pelo fato de o Java usar apagamento de tipo, mas saber que pelo menos o Reflection me anima novamente: D E agora, com lambdas no Java 8, a linguagem está realmente se adaptando ao desenvolvimento moderno. A única coisa que falta agora é o suporte nativo a getters e setters, ou propriedades, como são conhecidas em C #.
7hi4g0
120
Não é justo -1. Henrik provavelmente não está defendendo as exceções de esmagamento e não escreveu nada para elas, porque ele está apenas tentando demonstrar reflexão.
atraiu
70
Mais um por mostrar algumas possíveis exceções. Se eu tivesse escrito isso, seria ... catch (Exceção e) {... #
mikbanUtah
1
Eu recebi "variável pode não ter sido inicializada" para o methodin method.invoke(obj, arg1, arg2,...);. a method = null;resolve o problema, mas mencioná-lo na resposta não é uma má idéia.
Amin
2
@ DeaMon1 Os métodos Java não usam "códigos de saída", mas se o método retornar algo, invokeretornará o que foi retornado. Se ocorrer uma exceção ao executar o método, a exceção será agrupada em um InvocationTargetException.
ThePyroEagle
194

Use a invocação de método da reflexão:

Class<?> c = Class.forName("class name");
Method method = c.getDeclaredMethod("method name", parameterTypes);
method.invoke(objectToInvokeOn, params);

Onde:

  • "class name" é o nome da classe
  • objectToInvokeOn é do tipo Object e é o objeto em que você deseja chamar o método
  • "method name" é o nome do método que você deseja chamar
  • parameterTypesé do tipo Class[]e declara os parâmetros que o método leva
  • paramsé do tipo Object[]e declara os parâmetros a serem passados ​​para o método
Owen
fonte
Legal, eu acho que você está certo com getDeclaredMethod (), ele é provavelmente 'mais seguro' do que getMethod () ..
brasskazoo
22
Errado. Sim, o getDeclaredMethod funciona com métodos privados e protegidos. MAS: não funciona com métodos definidos nas superclasses (métodos herdados). Portanto, depende muito do que você deseja fazer. Em muitos casos, você deseja que ele funcione independentemente da classe exata em que o método está definido.
jrudolph
E onde devo colocar o arquivo "class"? de preferência explicar para o Eclipse IDE
Dr.jacky
@ Mr.Hyde no caminho da classe.
Stijn de Witt
O que devo colocar dentro de e method.invoke () se o método que estou chamando não aceita nenhum parâmetro? Parece que ainda tenho que fornecer o segundo parâmetro, deve haver alguma matriz de objetos vazia?
Igor
101

Para aqueles que desejam um exemplo de código direto no Java 7:

Dog classe:

package com.mypackage.bean;

public class Dog {
    private String name;
    private int age;

    public Dog() {
        // empty constructor
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void printDog(String name, int age) {
        System.out.println(name + " is " + age + " year(s) old.");
    }
}

ReflectionDemo classe:

package com.mypackage.demo;

import java.lang.reflect.*;

public class ReflectionDemo {

    public static void main(String[] args) throws Exception {
        String dogClassName = "com.mypackage.bean.Dog";
        Class<?> dogClass = Class.forName(dogClassName); // convert string classname to class
        Object dog = dogClass.newInstance(); // invoke empty constructor

        String methodName = "";

        // with single parameter, return void
        methodName = "setName";
        Method setNameMethod = dog.getClass().getMethod(methodName, String.class);
        setNameMethod.invoke(dog, "Mishka"); // pass arg

        // without parameters, return string
        methodName = "getName";
        Method getNameMethod = dog.getClass().getMethod(methodName);
        String name = (String) getNameMethod.invoke(dog); // explicit cast

        // with multiple parameters
        methodName = "printDog";
        Class<?>[] paramTypes = {String.class, int.class};
        Method printDogMethod = dog.getClass().getMethod(methodName, paramTypes);
        printDogMethod.invoke(dog, name, 3); // pass args
    }
}

Resultado: Mishka is 3 year(s) old.


Você pode chamar o construtor com parâmetros desta maneira:

Constructor<?> dogConstructor = dogClass.getConstructor(String.class, int.class);
Object dog = dogConstructor.newInstance("Hachiko", 10);

Como alternativa, você pode remover

String dogClassName = "com.mypackage.bean.Dog";
Class<?> dogClass = Class.forName(dogClassName);
Object dog = dogClass.newInstance();

e fazer

Dog dog = new Dog();

Method method = Dog.class.getMethod(methodName, ...);
method.invoke(dog, ...);

Leitura sugerida: Criando novas instâncias de classe

prata
fonte
1
A melhor resposta aqui. Completo e conciso
Reuben JaMes Aveño Gruta
1
Correta Melhor resposta.
Dhara Patel
Onde você consegue Methodobjetos?
parlad
Do pacote de reflexão.
silver
55

O método pode ser chamado assim. Existem também mais possibilidades (verifique a API de reflexão), mas esta é a mais simples:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Assert;
import org.junit.Test;

public class ReflectionTest {

    private String methodName = "length";
    private String valueObject = "Some object";

    @Test
    public void testGetMethod() throws SecurityException, NoSuchMethodException, IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {
        Method m = valueObject.getClass().getMethod(methodName, new Class[] {});
        Object ret = m.invoke(valueObject, new Object[] {});
        Assert.assertEquals(11, ret);
    }



}
Petr Macek
fonte
7
+1 na única resposta que reconheceu que o OP não especificou "nenhum parâmetro" em sua pergunta (e porque era o que eu estava procurando também).
John Fitzpatrick
16

Primeiro não. Evite esse tipo de código. Também tende a ser um código muito ruim e inseguro (consulte a seção 6 das Diretrizes de codificação segura para a linguagem de programação Java, versão 2.0 ).

Se você precisar fazer isso, prefira java.beans à reflexão. O feijão envolve a reflexão, permitindo acesso relativamente seguro e convencional.

Tom Hawtin - linha de orientação
fonte
11
Discordo. É muito fácil escrever esse código para ser seguro, e eu o fiz em vários idiomas. Por exemplo, é possível criar um conjunto de métodos permitidos e permitir que um método seja chamado apenas se o nome estiver no conjunto. Ainda mais seguro (mas ainda assim simples), seria limitar cada método permitido a um estado específico e não permitir que o método fosse chamado, a menos que o thread / interface / usuário / o que se encaixasse nesses critérios.
JSON
Nunca seja tão categórico em relação a esses problemas. No momento, estou criando um programa simples para permitir que o usuário defina tarefas arbitrárias sobre objetos arbitrários usando interfaces da web. Eu sei que é, de fato, inseguro, mas o teste adequado é realizado assim que a configuração é recebida, e permite que um não programador configure facilmente as tarefas e também dá aos programas a capacidade de vincular classes personalizadas ao código genérico (esse é o parte em que uso reflexão para permitir que eles configurem quais métodos usar via interface da web) sem precisar atualizar a GUI.
DGoiko
14

Para concluir as respostas do meu colega, preste muita atenção a:

  • chamadas estáticas ou de instância (em um caso, você não precisa de uma instância da classe, no outro, pode precisar confiar em um construtor padrão existente que pode ou não estar lá)
  • chamada de método pública ou não pública (para a última, você precisa chamar setAccessible no método dentro de um bloco doPrivileged , outros findbugs não serão felizes )
  • encapsulando em mais uma exceção aplicável gerenciável, se você desejar retroceder as inúmeras exceções do sistema java (daí a CCException no código abaixo)

Aqui está um código java1.4 antigo que leva em conta esses pontos:

/**
 * Allow for instance call, avoiding certain class circular dependencies. <br />
 * Calls even private method if java Security allows it.
 * @param aninstance instance on which method is invoked (if null, static call)
 * @param classname name of the class containing the method 
 * (can be null - ignored, actually - if instance if provided, must be provided if static call)
 * @param amethodname name of the method to invoke
 * @param parameterTypes array of Classes
 * @param parameters array of Object
 * @return resulting Object
 * @throws CCException if any problem
 */
public static Object reflectionCall(final Object aninstance, final String classname, final String amethodname, final Class[] parameterTypes, final Object[] parameters) throws CCException
{
    Object res;// = null;
    try {
        Class aclass;// = null;
        if(aninstance == null)
        {
            aclass = Class.forName(classname);
        }
        else
        {
            aclass = aninstance.getClass();
        }
        //Class[] parameterTypes = new Class[]{String[].class};
    final Method amethod = aclass.getDeclaredMethod(amethodname, parameterTypes);
        AccessController.doPrivileged(new PrivilegedAction() {
    public Object run() {
                amethod.setAccessible(true);
                return null; // nothing to return
            }
        });
        res = amethod.invoke(aninstance, parameters);
    } catch (final ClassNotFoundException e) {
        throw new CCException.Error(PROBLEM_TO_ACCESS+classname+CLASS, e);
    } catch (final SecurityException e) {
        throw new CCException.Error(PROBLEM_TO_ACCESS+classname+GenericConstants.HASH_DIESE+ amethodname + METHOD_SECURITY_ISSUE, e);
    } catch (final NoSuchMethodException e) {
        throw new CCException.Error(PROBLEM_TO_ACCESS+classname+GenericConstants.HASH_DIESE+ amethodname + METHOD_NOT_FOUND, e);
    } catch (final IllegalArgumentException e) {
        throw new CCException.Error(PROBLEM_TO_ACCESS+classname+GenericConstants.HASH_DIESE+ amethodname + METHOD_ILLEGAL_ARGUMENTS+String.valueOf(parameters)+GenericConstants.CLOSING_ROUND_BRACKET, e);
    } catch (final IllegalAccessException e) {
        throw new CCException.Error(PROBLEM_TO_ACCESS+classname+GenericConstants.HASH_DIESE+ amethodname + METHOD_ACCESS_RESTRICTION, e);
    } catch (final InvocationTargetException e) {
    throw new CCException.Error(PROBLEM_TO_ACCESS+classname+GenericConstants.HASH_DIESE+ amethodname + METHOD_INVOCATION_ISSUE, e);
    } 
    return res;
}
VonC
fonte
12
Object obj;

Method method = obj.getClass().getMethod("methodName", null);

method.invoke(obj, null);
chickeninabiscuit
fonte
O objeto deve ter pelo menos valor / valores.
Lova Chittumuri 12/02/19
Isso funcionou muito bem para o que eu precisava. Eu tive uma classe que já foi instanciada e só precisava obter um método dela. Adicionar capturas de exceções é uma boa ideia aqui, mas, caso contrário, isso funcionou perfeitamente para mim. Acho que minha maneira de evitar exceções nulas era usar anuláveis, mas estava usando um intervalo muito restrito de nomes de métodos (literalmente, apenas um contador de 1 a 4).
Jain Waldrip
12
//Step1 - Using string funClass to convert to class
String funClass = "package.myclass";
Class c = Class.forName(funClass);

//Step2 - instantiate an object of the class abov
Object o = c.newInstance();
//Prepare array of the arguments that your function accepts, lets say only one string here
Class[] paramTypes = new Class[1];
paramTypes[0]=String.class;
String methodName = "mymethod";
//Instantiate an object of type method that returns you method name
 Method m = c.getDeclaredMethod(methodName, paramTypes);
//invoke method with actual params
m.invoke(o, "testparam");
anujin
fonte
8

Se você fizer a chamada várias vezes, poderá usar os novos manipuladores de métodos introduzidos no Java 7. A seguir, o método retornará uma String:

Object obj = new Point( 100, 200 );
String methodName = "toString";  
Class<String> resultType = String.class;

MethodType mt = MethodType.methodType( resultType );
MethodHandle methodHandle = MethodHandles.lookup().findVirtual( obj.getClass(), methodName, mt );
String result = resultType.cast( methodHandle.invoke( obj ) );

System.out.println( result );  // java.awt.Point[x=100,y=200]
Christian Ullenboom
fonte
1
Para futuros leitores; Se você se preocupa com o desempenho, vai querer usar invokeExactsempre que puder. Para isso, a assinatura do site de chamada deve corresponder exatamente ao tipo de identificador do método. Geralmente, é preciso um pouco de ajustes para chegar ao trabalho. Nesse caso, você precisaria converter o primeiro parâmetro com: methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(0, Object.class));e depois chamar comoString result = (String) methodHandle.invokeExact(obj);
Jorn Vernee
7

Isso soa como algo factível com o pacote Java Reflection.

http://java.sun.com/developer/technicalArticles/ALT/Reflection/index.html

Particularmente em Invocando métodos por nome:

importar java.lang.reflect. *;

public class method2 {
  public int add(int a, int b)
  {
     return a + b;
  }

  public static void main(String args[])
  {
     try {
       Class cls = Class.forName("method2");
       Class partypes[] = new Class[2];
        partypes[0] = Integer.TYPE;
        partypes[1] = Integer.TYPE;
        Method meth = cls.getMethod(
          "add", partypes);
        method2 methobj = new method2();
        Object arglist[] = new Object[2];
        arglist[0] = new Integer(37);
        arglist[1] = new Integer(47);
        Object retobj 
          = meth.invoke(methobj, arglist);
        Integer retval = (Integer)retobj;
        System.out.println(retval.intValue());
     }
     catch (Throwable e) {
        System.err.println(e);
     }
  }
}
zxcv
fonte
6

Indexação (mais rápida)

Você pode usar FunctionalInterfacepara salvar métodos em um contêiner para indexá-los. Você pode usar o contêiner de matriz para invocá-los por números ou o hashmap para invocá-los por seqüências de caracteres. Com esse truque, você pode indexar seus métodos para invocá-los dinamicamente mais rapidamente .

@FunctionalInterface
public interface Method {
    double execute(int number);
}

public class ShapeArea {
    private final static double PI = 3.14;

    private Method[] methods = {
        this::square,
        this::circle
    };

    private double square(int number) {
        return number * number;
    }

    private double circle(int number) {
        return PI * number * number;
    }

    public double run(int methodIndex, int number) {
        return methods[methodIndex].execute(aNumber);
    }
}

Sintaxe Lambda

Você também pode usar a sintaxe lambda:

public class ShapeArea {
    private final static double PI = 3.14;

    private Method[] methods = {
        number -> {
            return number * number;
        },
        number -> {
            return PI * number * number;
        },
    };

    public double run(int methodIndex, int number) {
        return methods[methodIndex].execute(aNumber);
    }
}
Amir Fo
fonte
1
Essa técnica parece muito melhor que a reflexão.
John O
É realmente muito melhor?
Dimitri Kopriwa
A indexação @DimitriKopriwa é a maneira como você usa a memória RAM em vez dos cálculos da CPU. Para indexação inteira, a dificuldade do algoritmo é O(1).
Amir Fo
5
Method method = someVariable.class.getMethod(SomeClass);
String status = (String) method.invoke(method);

SomeClassé a classe e someVariableé uma variável

Subrahmanya Prasad
fonte
se someVariable for realmente um objeto, chame someVariable.getClass (). Além disso, você não pode chamar getMethod () com uma classe como o único argumento. Nem invoque o método com o método Correto: someVariable.getClass (). GetMethod ("coolMethod", parameterClasses) .invoke (argumentos);
Orangle
5

Eu faço assim:

try {
    YourClass yourClass = new YourClass();
    Method method = YourClass.class.getMethod("yourMethodName", ParameterOfThisMethod.class);
    method.invoke(yourClass, parameter);
} catch (Exception e) {
    e.printStackTrace();
}
Marcel
fonte
5

Consulte o seguinte código pode ajudá-lo.

public static Method method[];
public static MethodClass obj;
public static String testMethod="A";

public static void main(String args[]) 
{
    obj=new MethodClass();
    method=obj.getClass().getMethods();
    try
    {
        for(int i=0;i<method.length;i++)
        {
            String name=method[i].getName();
            if(name==testMethod)
            {   
                method[i].invoke(name,"Test Parameters of A");
            }
        }
    }
    catch(Exception ex)
    {
        System.out.println(ex.getMessage());
    }
}

Obrigado....

Rahul Karankal
fonte
Não é assim que você compara Strings em Java. Você deve usar o método .equals. Caso contrário, você está apenas comparando que elas são a mesma referência de objeto e realmente não se importa com referências a objetos - apenas o conteúdo da string sendo uma correspondência. Você também pode obter o método pelo nome por meio de reflexão, por isso não sabe por que criar o seu próprio?
Lo-Tan
5

Aqui estão os métodos PRONTOS PARA USAR:

Para invocar um método, sem argumentos:

public static void callMethodByName(Object object, String methodName) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    object.getClass().getDeclaredMethod(methodName).invoke(object);
}

Para invocar um método, com Argumentos:

    public static void callMethodByName(Object object, String methodName, int i, String s) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        object.getClass().getDeclaredMethod(methodName, int.class, String.class).invoke(object, i, s);
    }

Use os métodos acima como abaixo:

package practice;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class MethodInvoke {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
        String methodName1 = "methodA";
        String methodName2 = "methodB";
        MethodInvoke object = new MethodInvoke();
        callMethodByName(object, methodName1);
        callMethodByName(object, methodName2, 1, "Test");
    }

    public static void callMethodByName(Object object, String methodName) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        object.getClass().getDeclaredMethod(methodName).invoke(object);
    }

    public static void callMethodByName(Object object, String methodName, int i, String s) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        object.getClass().getDeclaredMethod(methodName, int.class, String.class).invoke(object, i, s);
    }

    void methodA() {
        System.out.println("Method A");
    }

    void methodB(int i, String s) {
        System.out.println("Method B: "+"\n\tParam1 - "+i+"\n\tParam 2 - "+s);
    }
}

Resultado:

Método A  
Método B:  
	Param1 - 1  
	Parâmetro 2 - Teste
Sandeep Nalla
fonte
3

Student.java

class Student{
    int rollno;
    String name;

    void m1(int x,int y){
        System.out.println("add is" +(x+y));
    }

    private void m3(String name){
        this.name=name;
        System.out.println("danger yappa:"+name);
    }
    void m4(){
        System.out.println("This is m4");
    }
}

StudentTest.java

import java.lang.reflect.Method;
public class StudentTest{

     public static void main(String[] args){

        try{

            Class cls=Student.class;

            Student s=(Student)cls.newInstance();


            String x="kichha";
            Method mm3=cls.getDeclaredMethod("m3",String.class);
            mm3.setAccessible(true);
            mm3.invoke(s,x);

            Method mm1=cls.getDeclaredMethod("m1",int.class,int.class);
            mm1.invoke(s,10,20);

        }
        catch(Exception e){
            e.printStackTrace();
        }
     }
}
user8387971
fonte
1

Você deve usar reflexão - inicie um objeto de classe, depois um método nesta classe e, em seguida, invoque esse método em um objeto com parâmetros opcionais . Lembre-se de agrupar o seguinte snippet no bloco try-catch

Espero que ajude!

Class<?> aClass = Class.forName(FULLY_QUALIFIED_CLASS_NAME);
Method method = aClass.getMethod(methodName, YOUR_PARAM_1.class, YOUR_PARAM_2.class);
method.invoke(OBJECT_TO_RUN_METHOD_ON, YOUR_PARAM_1, YOUR_PARAM_2);
detman
fonte
1

Isso está funcionando bem para mim:

public class MethodInvokerClass {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException, InvocationTargetException, InstantiationException {
        Class c = Class.forName(MethodInvokerClass.class.getName());
        Object o = c.newInstance();
        Class[] paramTypes = new Class[1];
        paramTypes[0]=String.class;
        String methodName = "countWord";
         Method m = c.getDeclaredMethod(methodName, paramTypes);
         m.invoke(o, "testparam");
}
public void countWord(String input){
    System.out.println("My input "+input);
}

}

Resultado:

My input testparam

Eu sou capaz de invocar o método passando seu nome para outro método (como principal).

Laxman G
fonte
1

usando import java.lang.reflect.*;

public static Object launchProcess(String className, String methodName, Class<?>[] argsTypes, Object[] methodArgs)
        throws Exception {

    Class<?> processClass = Class.forName(className); // convert string classname to class
    Object process = processClass.newInstance(); // invoke empty constructor

    Method aMethod = process.getClass().getMethod(methodName,argsTypes);
    Object res = aMethod.invoke(process, methodArgs); // pass arg
    return(res);
}

e aqui está como você o usa:

String className = "com.example.helloworld";
String methodName = "print";
Class<?>[] argsTypes = {String.class,  String.class};
Object[] methArgs = { "hello", "world" };   
launchProcess(className, methodName, argsTypes, methArgs);
dina
fonte
0

Com o jooR , é apenas:

on(obj).call(methodName /*params*/).get()

Aqui está um exemplo mais elaborado:

public class TestClass {

    public int add(int a, int b) { return a + b; }
    private int mul(int a, int b) { return a * b; }
    static int sub(int a, int b) { return a - b; }

}

import static org.joor.Reflect.*;

public class JoorTest {

    public static void main(String[] args) {
        int add = on(new TestClass()).call("add", 1, 2).get(); // public
        int mul = on(new TestClass()).call("mul", 3, 4).get(); // private
        int sub = on(TestClass.class).call("sub", 6, 5).get(); // static
        System.out.println(add + ", " + mul + ", " + sub);
    }
}

Isso imprime:

3, 12, 1

Andronicus
fonte
-10

para mim, uma maneira bastante simples e à prova de idiotas seria simplesmente criar um método de chamada de método assim:

public static object methodCaller(String methodName)
{
    if(methodName.equals("getName"))
        return className.getName();
}

então, quando você precisar chamar o método, basta colocar algo assim

//calling a toString method is unnessary here, but i use it to have my programs to both rigid and self-explanitory 
System.out.println(methodCaller(methodName).toString()); 
SMayne
fonte
4
Se a instância já é conhecida durante a compilação, por que você simplesmente não faz className.getName().toString()? Você está perdendo todo o ponto de reflexão.
BalusC
Como eu disse, desnecessário neste caso, mas supondo que você sempre saiba que a instância é um mau hábito de programação.
SMayne
2
@ Samayne: Eu sugeriria excluir este post.
Lpapp 18/04
programação ruim preferiria ser um elogio neste caso
pdenti