¿Cómo invoco un método Java cuando se me da el nombre del método como una cadena?

684

Si tengo dos variables:

Object obj;
String methodName = "getName";

Sin conocer la clase de obj, ¿cómo puedo llamar al método identificado por methodNameél?

El método que se llama no tiene parámetros y tiene un Stringvalor de retorno. Es un captador para un bean Java .

brasskazoo
fuente
3
O use la API de reflexión o use groovy
Peter Kelley

Respuestas:

972

Codificación desde la cadera, sería algo así como:

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

Los parámetros identifican el método muy específico que necesita (si hay varios sobrecargados disponibles, si el método no tiene argumentos, solo dé methodName).

Luego invocas ese método llamando

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

Nuevamente, omita los argumentos .invoke, si no tiene ninguno. Pero sí. Leer sobre Java Reflection

Henrik Paul
fuente
2
Estaba un poco molesto por el hecho de que Java usa el borrado de tipo, pero saber que al menos tiene Reflection me anima de nuevo: D Y ahora con lambdas en Java 8, el lenguaje realmente se está poniendo al día con el desarrollo moderno. Lo único que falta ahora es el soporte nativo para getters y setters, o propiedades como se conocen en C #.
7hi4g0 01 de
120
No es justo -1. Henrik probablemente no defiende las excepciones de aplastamiento y no escribió nada para ellos porque solo está tratando de demostrar reflexión.
Dibujó
70
Más uno por mostrar algunas posibles excepciones. Si hubiera escrito esto, sería ... captura (Excepción e) {...
mikbanUtah
1
Obtuve "la variable puede no haberse inicializado" para el methodin method.invoke(obj, arg1, arg2,...);. A method = null;resuelve el problema, pero mencionarlo en la respuesta no es una mala idea.
Amin
2
@ DeaMon1 Los métodos Java no usan "códigos de salida", pero si el método devuelve algo, invokedevolverá lo que haya devuelto. Si se produce una excepción al ejecutar el método, la excepción se envolverá en un InvocationTargetException.
ThePyroEagle
194

Utilice la invocación del método desde la reflexión:

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

Dónde:

  • "class name" es el nombre de la clase
  • objectToInvokeOn es de tipo Object y es el objeto sobre el que desea invocar el método
  • "method name" es el nombre del método al que quieres llamar
  • parameterTypeses de tipo Class[]y declara los parámetros que toma el método
  • paramses de tipo Object[]y declara los parámetros que se pasarán al método
Owen
fuente
Genial, creo que tienes razón con getDeclaredMethod (), probablemente sea 'más seguro' que getMethod () ..
brasskazoo
22
Incorrecto. Sí, getDeclaredMethod funciona con métodos privados y protegidos. PERO: no funciona con métodos definidos en superclases (métodos heredados). Por lo tanto, depende en gran medida de lo que quieras hacer. En muchos casos, desea que funcione independientemente de la clase exacta en la que se define el método.
jrudolph
¿Y dónde debo poner el archivo "clase"? preferiblemente explique para Eclipse IDE
Dr.jacky
@ Mr.Hyde en el camino de clase.
Stijn de Witt
¿Qué debo poner dentro de y method.invoke () si el método al que llamo no acepta ningún parámetro? Parece que todavía tengo que proporcionar el segundo parámetro, ¿debería ser una matriz de objetos vacía?
Igor
101

Para aquellos que desean un ejemplo de código directo en Java 7:

Dog clase:

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 clase:

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
    }
}

Salida: Mishka is 3 year(s) old.


Puede invocar al constructor con parámetros de esta manera:

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

Alternativamente, puedes eliminar

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

y hacer

Dog dog = new Dog();

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

Lectura sugerida: creación de nuevas instancias de clase

plata
fuente
1
La mejor respuesta aquí. Completo y conciso
Reuben JaMes Aveño Gruta
1
La mejor respuesta correcta.
Dhara Patel
¿De dónde sacas el Methodobjeto?
Parlad
Del paquete de reflejos.
plata
55

El método se puede invocar así. También hay más posibilidades (verifique la API de reflexión), pero esta es la más simple:

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
fuente
77
+1 para la única respuesta que reconoció que el OP especificó "sin parámetros" en su pregunta (y porque era lo que estaba buscando también).
John Fitzpatrick
16

Primero, no lo hagas. Evita este tipo de código. Tiende a ser un código realmente malo e inseguro también (consulte la sección 6 de las Pautas de codificación segura para el lenguaje de programación Java, versión 2.0 ).

Si debe hacerlo, prefiera java.beans a la reflexión. Los frijoles envuelven la reflexión permitiendo un acceso relativamente seguro y convencional.

Tom Hawtin - tackline
fuente
11
Estoy en desacuerdo. Es muy fácil escribir dicho código para que sea seguro y lo he hecho en varios idiomas. Por ejemplo, uno podría hacer un conjunto de métodos permitidos, y solo permitir que se invoque un método si su nombre está en el conjunto. Aún más seguro (pero aún así simple) sería limitar cada método permitido a un estado específico, y no permitir que se invoque el método a menos que el hilo / interfaz / usuario / lo que se ajuste a tales criterios.
JSON
Nunca seas tan categórico sobre tales temas. En este momento estoy creando un programa simple para permitir al usuario definir tareas arbitrarias sobre objetos arbitrarios utilizando interfaces web. Sé que, de hecho, es inseguro, pero las pruebas adecuadas se realizan una vez que se recibe la configuración, y le permite a un no programador configurar fácilmente las tareas, y también le da a los programas la capacidad de vincular clases personalizadas con el código genérico (ese es el uso la reflexión para, para permitirles configurar qué métodos usar a través de la interfaz web) sin tener que actualizar la GUI.
DGoiko
14

Para completar las respuestas de mi colega, es posible que desee prestar mucha atención a:

  • llamadas estáticas o de instancia (en un caso, no necesita una instancia de la clase, en el otro, es posible que deba confiar en un constructor predeterminado existente que puede o no estar allí)
  • llamada de método público o no público (para este último, debe llamar a setAccessible en el método dentro de un bloque doPrivileged , otros findbugs no estarán contentos )
  • encapsulando en una excepción aplicativa más manejable si desea arrojar las numerosas excepciones del sistema java (de ahí la excepción CCE en el código a continuación)

Aquí hay un viejo código java1.4 que tiene en cuenta esos puntos:

/**
 * 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
fuente
12
Object obj;

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

method.invoke(obj, null);
galletas de pollo
fuente
El objeto debe tener al menos valor / valores.
Lova Chittumuri
Esto funcionó muy bien para lo que necesitaba. Tenía una clase que ya estaba instanciada y solo necesitaba sacar un método de ella. Agregar capturas para excepciones es una buena idea aquí, pero de lo contrario, esto funcionó perfectamente para mí. Creo que mi forma de evitar excepciones nulas fue usar nulables, pero estaba usando un rango muy restringido de nombres de métodos (literalmente solo un 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
fuente
8

Si realiza la llamada varias veces, puede usar los nuevos identificadores de método introducidos en Java 7. Aquí vamos a su método que devuelve una Cadena:

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
fuente
1
Para futuros lectores; Si te importa el rendimiento, querrás usar invokeExactsiempre que puedas. Para eso, la firma del sitio de llamada tiene que coincidir exactamente con el tipo de identificador de método. Por lo general, toma un poco de tiempo para llegar al trabajo. En este caso, tendría que emitir el primer parámetro con: methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(0, Object.class));y luego invocar comoString result = (String) methodHandle.invokeExact(obj);
Jorn Vernee
7

Esto suena como algo factible con el paquete Java Reflection.

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

Particularmente en Métodos de invocación por nombre:

import 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
fuente
6

Indexación (más rápido)

Puede usar FunctionalInterfacepara guardar métodos en un contenedor para indexarlos. Puede usar el contenedor de matriz para invocarlos por números o hashmap para invocarlos por cadenas. Con este truco, puede indexar sus métodos para invocarlos dinámicamente más rápido .

@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);
    }
}

Sintaxis Lambda

También puede usar la sintaxis 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
fuente
1
Esta técnica parece mucho mejor que la reflexión.
John O
¿Es realmente mucho mejor?
Dimitri Kopriwa
@DimitriKopriwa Indexing es la forma en que usa ram en lugar de los cálculos de la CPU. Para la indexación de enteros, la dificultad del algoritmo es O(1).
Amir Fo
5
Method method = someVariable.class.getMethod(SomeClass);
String status = (String) method.invoke(method);

SomeClasses la clase y someVariablees una variable

Subrahmanya Prasad
fuente
si someVariable es realmente un objeto, llame a someVariable.getClass (). Además, no puede llamar a getMethod () con una clase como único argumento. Ni invocar método con método. Correcto: someVariable.getClass (). GetMethod ("coolMethod", parameterClasses) .invoke (argumentos);
Orangle
5

Hago esto así:

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

Consulte el siguiente código que puede ayudarlo.

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());
    }
}

Gracias....

Rahul Karankal
fuente
No es así como se comparan las cadenas en Java. Debe usar el método .equals. De lo contrario, solo está comparando que son la misma referencia de objeto, y en realidad no le importan las referencias de objeto, solo el contenido de la cadena es una coincidencia. También puede obtener el método por nombre a través de la reflexión, por lo que no está seguro de por qué rodaría el suyo.
Lo-Tan
5

Aquí están los MÉTODOS LISTOS PARA USAR:

Para invocar un método, sin argumentos:

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

Para invocar un método, con 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);
    }

Utilice los métodos anteriores de la siguiente manera:

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);
    }
}

Salida:

Método A  
Método B:  
	Param1 - 1  
	Param 2 - Prueba
Sandeep Nalla
fuente
3

Estudiante.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();
        }
     }
}
usuario8387971
fuente
1

Debe usar la reflexión: inicie un objeto de clase, luego un método en esta clase y luego invoque este método en un objeto con parámetros opcionales . Recuerde envolver el siguiente fragmento en el bloque try-catch

¡Espero eso ayude!

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
fuente
1

Esto está funcionando bien para mí:

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);
}

}

Salida:

My input testparam

Puedo invocar el método pasando su nombre a otro método (como main).

Laxman G
fuente
1

utilizando 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);
}

y así es como lo usas:

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

Con jooR es simplemente:

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

Aquí hay un ejemplo más 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);
    }
}

Esto imprime:

3, 12, 1

Andronicus
fuente
-10

para mí, una manera bastante simple y a prueba de tontos sería simplemente hacer un método de método de llamada así:

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

entonces cuando necesites llamar al método simplemente pon algo como esto

//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
fuente
44
Si la instancia ya se conoce durante el tiempo de compilación, ¿por qué no lo haces className.getName().toString()? Te estás perdiendo todo el punto de reflexión.
BalusC
Como dije, innecesario en este caso, pero suponiendo que siempre sepas que la instancia es un mal hábito de programación.
SMayne
2
@SMayne: Sugeriría eliminar esta publicación.
lpapp
una mala programación preferiría ser un cumplido en este caso
pdenti