Come posso invocare un metodo Java quando mi viene dato il nome del metodo come stringa?


684

Se ho due variabili:

Object obj;
String methodName = "getName";

Senza conoscere la classe di obj, come posso chiamare il metodo identificato da methodNamesu di esso?

Il metodo chiamato non ha parametri e un Stringvalore restituito. È un getter per un bean Java .


Risposte:


972

Codifica dall'anca, sarebbe qualcosa del tipo:

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

I parametri identificano il metodo molto specifico necessario (se sono disponibili diversi overload, se il metodo non ha argomenti, fornire solo methodName).

Quindi invochi quel metodo chiamando

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

Ancora una volta, lascia fuori gli argomenti .invoke, se non ne hai. Ma si. Leggi Java Reflection


2
Sono stato un po 'sconvolto dal fatto che Java utilizza la cancellazione del tipo, ma sapendo che almeno ha Reflection mi rallegra di nuovo: D E ora con lambda in Java 8 il linguaggio si sta davvero aggiornando con lo sviluppo moderno. L'unica cosa che manca ora è il supporto nativo per getter e setter o proprietà come sono conosciuti in C #.
7hi4g0

120
Non è giusto -1. Henrik probabilmente non sta sostenendo eccezioni schiaccianti e non ha scritto nulla per loro perché sta solo cercando di dimostrare la riflessione.
disegnò

70
Più uno per mostrare alcune potenziali eccezioni. Se avessi scritto questo, sarebbe ... catch (Exception e) {...
mikbanUtah

1
Ho ottenuto "la variabile potrebbe non essere stata inizializzata" per methodin method.invoke(obj, arg1, arg2,...);. a method = null;risolve il problema, ma menzionarlo nella risposta non è una cattiva idea.
Amin,

2
@ DeaMon1 I metodi Java non usano i "codici di uscita", ma se il metodo restituisce qualcosa, invokerestituirà qualunque cosa abbia restituito. Se si verifica un'eccezione durante l'esecuzione del metodo, l'eccezione verrà racchiusa in un InvocationTargetException.
ThePyroEagle il

194

Usa il metodo di invocazione da reflection:

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

Dove:

  • "class name" è il nome della classe
  • objectToInvokeOn è di tipo Object ed è l'oggetto su cui si desidera invocare il metodo
  • "method name" è il nome del metodo che si desidera chiamare
  • parameterTypesè di tipo Class[]e dichiara i parametri che il metodo accetta
  • paramsè di tipo Object[]e dichiara i parametri da passare al metodo

Bene, penso che tu abbia ragione con getDeclaredMethod (), probabilmente è 'più sicuro' di getMethod () ..
brasskazoo,

22
Sbagliato. Sì, getDeclaredMethod funziona con metodi privati ​​e protetti. MA: non funziona con i metodi definiti nelle superclassi (metodi ereditati). Quindi, dipende fortemente da cosa vuoi fare. In molti casi si desidera che funzioni indipendentemente dalla classe esatta in cui è definito il metodo.
jrudolph,

E dove dovrei mettere il file "class"? preferibilmente spiegare per Eclipse IDE
Dr.jacky

@ Mr.Hyde sul percorso di classe.
Stijn de Witt,

Cosa devo mettere all'interno di e method.invoke () se il metodo che sto chiamando non accetta alcun parametro? Sembra che debba ancora fornire il secondo parametro, dovrebbe essere un array di oggetti vuoto?
Igor,

101

Per coloro che desiderano un esempio di codice diretto in 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
    }
}

Produzione: Mishka is 3 year(s) old.


È possibile richiamare il costruttore con parametri in questo modo:

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

In alternativa, puoi rimuoverlo

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

e fai

Dog dog = new Dog();

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

Lettura consigliata: creazione di nuove istanze di classe


1
La migliore risposta qui. Completo e conciso
Reuben JaMes Aveño Gruta,

1
Risposta migliore corretta.
Dhara Patel,

Dove prendi l' Methodoggetto?
parlad

Dal riflesso pkg.
argento

55

Il metodo può essere invocato in questo modo. Ci sono anche più possibilità (controlla l'api di riflessione), ma questa è la più semplice:

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



}

7
+1 per l'unica risposta che ha riconosciuto che l'OP ha specificato "nessun parametro" nella sua domanda (e perché era anche quello che stavo cercando).
John Fitzpatrick,

16

Per prima cosa no. Evita questo tipo di codice. Tende a essere davvero un codice errato e anche insicuro (vedere la sezione 6 delle Linee guida per la codifica sicura per Java Programming Language, versione 2.0 ).

Se devi farlo, preferisci java.beans alla riflessione. I fagioli avvolgono la riflessione consentendo un accesso relativamente sicuro e convenzionale.


11
Non sono d'accordo. È molto facile scrivere un codice del genere per sicurezza e l'ho fatto in più lingue. Ad esempio, è possibile creare un set di metodi consentiti e consentire l'invocazione di un metodo solo se il nome è nel set. Ancora più sicuro (eppure semplice come la testa di un osso) limiterebbe ogni metodo consentito a uno stato specifico e non consentirebbe di invocare il metodo a meno che il thread / interfaccia / utente / qualunque cosa si adatti a tali criteri.
JSON,

Non essere mai così categorico su tali problemi. In questo momento sto creando un semplice programma per consentire all'utente di definire attività arbitrarie su oggetti arbitrari utilizzando interfacce web. So che è, in effetti, insicuro, ma una volta ricevuto il config viene eseguito un test adeguato, che consente a un non programmatore di configurare facilmente le attività e offre inoltre ai programmi la possibilità di collegare classi personalizzate al codice generico (questo è il parte a cui uso la riflessione, per consentire loro di configurare quali metodi utilizzare tramite l'interfaccia web) senza dover aggiornare la GUI.
DGoiko,

14

Per completare le risposte del mio collega, potresti voler prestare molta attenzione a:

  • chiamate statiche o di istanza (in un caso non è necessaria un'istanza della classe, nell'altro potrebbe essere necessario fare affidamento su un costruttore predefinito esistente che potrebbe essere presente o meno)
  • chiamata di metodo pubblica o non pubblica (per quest'ultima, è necessario chiamare setAccessible sul metodo all'interno di un blocco doPrivileged , altri findbugs non saranno contenti )
  • incapsulando in un'eccezione applicativa più gestibile se si desidera respingere le numerose eccezioni del sistema java (da cui il CCException nel codice seguente)

Ecco un vecchio codice java1.4 che tiene conto di questi punti:

/**
 * 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;
}

12
Object obj;

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

method.invoke(obj, null);

L'oggetto dovrebbe avere almeno valore / valori.
Lova Chittumuri,

Questo ha funzionato davvero bene per quello di cui avevo bisogno. Avevo una classe che era già stata istanziata e avevo solo bisogno di ricavarne un metodo. Aggiungere catture per eccezioni è una buona idea qui, ma per il resto, ha funzionato perfettamente per me. Penso che il mio modo di evitare eccezioni nulle fosse usare nullable, ma stavo usando una gamma molto ristretta di nomi di metodi (letteralmente solo un contatore da 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");

8

Se si effettua più volte la chiamata, è possibile utilizzare i nuovi handle di metodo introdotti in Java 7. Ecco il metodo che restituisce una stringa:

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]

1
Ai futuri lettori; Se ti interessa la performance, vorrai usarla invokeExactogni volta che puoi. Per questo, la firma del sito di chiamata deve corrispondere esattamente al tipo di handle del metodo. Di solito ci vuole un po 'a armeggiare per mettersi al lavoro. In questo caso avresti bisogno di lanciare il primo parametro con: methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(0, Object.class));e quindi invocare likeString result = (String) methodHandle.invokeExact(obj);
Jorn Vernee

7

Sembra qualcosa che sia fattibile con il pacchetto Java Reflection.

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

Soprattutto sotto Invocando metodi per nome:

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

6

Indicizzazione (più veloce)

È possibile utilizzare FunctionalInterfaceper salvare i metodi in un contenitore per indicizzarli. È possibile utilizzare il contenitore array per richiamarli tramite numeri o hashmap per richiamarli mediante stringhe. Con questo trucco, puoi indicizzare i tuoi metodi per invocarli dinamicamente più velocemente .

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

Sintassi lambda

Puoi anche usare la sintassi 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);
    }
}

1
Questa tecnica sembra molto meglio della riflessione.
John O

È davvero molto meglio?
Dimitri Kopriwa,

@DimitriKopriwa L'indicizzazione è il modo in cui usi ram invece dei calcoli della CPU. Per l'indicizzazione di numeri interi, la difficoltà dell'algoritmo è O(1).
Amir Fo

5
Method method = someVariable.class.getMethod(SomeClass);
String status = (String) method.invoke(method);

SomeClassè la classe ed someVariableè una variabile.


se someVariable è davvero un oggetto, chiama someVariable.getClass (). Inoltre, non è possibile chiamare getMethod () con una classe come unico argomento. Né invocare il metodo con il metodo. Corretto: someVariable.getClass (). GetMethod ("coolMethod", parameterClasses) .invoke (argomenti);
Orangle

5

Lo faccio in questo modo:

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

5

Si prega di fare riferimento al seguente codice può essere d'aiuto.

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

Grazie....


Non è così che si confrontano le stringhe in Java. È necessario utilizzare il metodo .equals. Altrimenti, stai solo confrontando che sono lo stesso riferimento ad oggetto, e in realtà non ti importa dei riferimenti ad oggetto - solo il contenuto della stringa è una corrispondenza. Puoi anche ottenere il metodo per nome tramite la riflessione, quindi non sei sicuro del motivo per cui vorresti creare il tuo?
Lo-Tan,

5

Ecco i METODI PRONTI ALL'USO:

Per invocare un metodo, senza Argomenti:

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

Per invocare un metodo, con Argomenti:

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

Utilizzare i metodi sopra indicati come di seguito:

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

Produzione:

Metodo A  
Metodo B:  
	Param1 - 1  
	Param 2 - Test

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

1

È necessario utilizzare reflection: init un oggetto di classe, quindi un metodo in questa classe e quindi invocare questo metodo su un oggetto con parametri opzionali . Ricorda di racchiudere il seguente frammento nel blocco try-catch

Spero che sia d'aiuto!

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

1

Questo funziona bene per me:

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

}

Produzione:

My input testparam

Sono in grado di invocare il metodo passando il suo nome a un altro metodo (come principale).


1

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

ed ecco come lo usi:

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

0

Con jooR è semplicemente:

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

Ecco un esempio più elaborato:

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

Questo stampa:

3, 12, 1


-10

per me un modo piuttosto semplice e a prova di folle sarebbe semplicemente quello di creare un metodo di chiamata del metodo in questo modo:

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

quindi quando hai bisogno di chiamare il metodo semplicemente metti qualcosa del genere

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

4
Se l'istanza è già nota durante il compiletime, perché non lo fai className.getName().toString()? Ti manca l'intero punto di riflessione.
BalusC,

Come ho detto, in questo caso non è necessario, ma supponendo che saprai sempre che l'istanza è una cattiva abitudine di programmazione.
SMayne,

2
@Say: suggerirei di eliminare questo post.
LPAP

una cattiva programmazione sarebbe piuttosto un complimento in questo caso
pdenti,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.