Як викликати метод Java, коли ім'я методу дано як рядок?


684

Якщо у мене є дві змінні:

Object obj;
String methodName = "getName";

Не знаючи класу obj, як я можу назвати метод, визначений methodNameна ньому?

Метод, що викликається, не має параметрів і Stringзворотного значення. Це збирач для квасолі Java .


3
Або використовуйте відображення api або використовуйте groovy
Пітер Келлі

Відповіді:


972

Кодування з стегна, це було б щось на зразок:

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

Параметри ідентифікують той самий конкретний метод, який вам потрібен (якщо є кілька перевантажених, якщо у методу немає аргументів, лише дайте methodName ).

Потім ви викликаєте цей метод, зателефонувавши

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

Знову ж таки, залиште аргументи .invoke, якщо у вас їх немає. Але так. Читайте про відображення Java


2
Трохи засмутився той факт, що Java використовує стирання типу, але знаючи, що принаймні це відображення, це знову мене підбадьорює: D А тепер з лямбдами на Java 8 мова справді набирає швидкість із сучасним розвитком. Єдине, чого зараз не вистачає, - це вбудована підтримка жителів та сеттерів, або властивості, як вони відомі в C #.
7hi4g0

120
Не справедливий -1. Генрік, напевно, не виступає за винятки, що склалися, і не писав для них нічого, тому що він просто намагається продемонструвати роздуми.
малював

70
Плюс один для показу деяких можливих винятків. Якби я це написав, це було б ... лов (виняток е) {...
mikbanUtah

1
Я отримав "змінну, можливо, не було ініціалізовано" для methodв method.invoke(obj, arg1, arg2,...);. а method = null;вирішує проблему , але згадки про нього у відповідь не погана ідея.
Амін

2
@ DeaMon1 Методи Java не використовують "коди виходу", але якщо метод поверне щось, invokeповерне те, що він повернув. Якщо виняток працює за допомогою методу, виняток буде загорнуто у InvocationTargetException.
ThePyroEagle

194

Використовувати виклик методу з відображення:

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

Де:

  • "class name" - це назва класу
  • objectToInvokeOn є об'єктом типу та є об'єктом, до якого потрібно викликати метод
  • "method name" - це назва методу, який ви хочете зателефонувати
  • parameterTypesмає тип Class[]і оголошує параметри, які приймає метод
  • paramsмає тип Object[]і оголошує параметри, що передаються методу

Класно, я думаю, ти маєш рацію з getDeclaredMethod (), це, мабуть, "безпечніше", ніж getMethod () ..
brasskazoo

22
Неправильно. Так, getDeclaredMethod працює з приватними та захищеними методами. АЛЕ: це не працює з методами, визначеними в суперкласах (успадковані методи). Отже, це сильно залежить від того, що ви хочете зробити. У багатьох випадках ви хочете, щоб він працював незалежно від точного класу, в якому визначений метод.
jrudolph

І куди я повинен помістити файл "класу"? бажано пояснити для Eclipse IDE
Dr.jacky

@ Mr.Hyde на шляху класу.
Штійн де Вітт

Що потрібно поставити всередині і method.invoke (), якщо метод, який я викликаю, взагалі не приймає жодних параметрів? Здається, що я все ж повинен надати другий параметр, чи повинен це бути якийсь порожній масив Object?
Ігор

101

Для тих, хто хоче приклад прямого коду в Java 7:

Dog клас:

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 клас:

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

Вихід: Mishka is 3 year(s) old.


Ви можете викликати конструктор з параметрами таким чином:

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

Можна також видалити

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

і робити

Dog dog = new Dog();

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

Пропоноване читання: Створення екземплярів нового класу


1
Тут найкраща відповідь. Повна та лаконічна
Reuben JaMes Aveño Gruta

1
Правильна найкраща відповідь.
Дхара Патель

Де ви берете Methodпредмет?
парлада

З відображенням кг.
срібло

55

Метод можна викликати так. Існує також більше можливостей (перевірити api відображення), але це найпростіший:

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 за єдину відповідь, яка визнала, що ОП вказала "немає параметрів" у своєму питанні (і тому, що я теж шукав).
Джон Фіцпатрік

16

По-перше, не варто. Уникайте подібного коду. Це, як правило, дуже поганий код і занадто небезпечний (див. Розділ 6 Правил безпечного кодування для мови програмування Java, версія 2.0 ).

Якщо ви повинні це зробити, віддайте перевагу рефлексії java.beans. Боби обертають відображення, що дозволяє відносно безпечний та звичайний доступ.


11
Я не погоджуюсь. Дуже легко написати такий код, щоб бути безпечним, і я це робив на кількох мовах. Наприклад, можна зробити набір допустимих методів, і дозволити виклик методу лише у тому випадку, якщо його ім'я є в наборі. Ще більш захищеним (але все ще простий кістковий голова) було б обмеження кожного дозволеного методу певним станом, і не дозволяти застосувати метод, якщо нитка / інтерфейс / користувач / що не відповідає таким критеріям.
JSON

Ніколи не будьте настільки категоричні щодо таких питань. Зараз я створюю просту програму, яка дозволяє користувачеві визначати довільні завдання над довільними об'єктами за допомогою веб-інтерфейсів. Я знаю, що це насправді небезпечно, але належне тестування проводиться після отримання конфігурації, і це дозволяє непрограмісту легко налаштувати завдання, а також надає програмам можливість пов'язувати власні класи з загальним кодом (ось частина Я використовую роздуми, щоб дозволити їм налаштувати, які методи використовувати через веб-інтерфейс) без необхідності оновлення графічного інтерфейсу.
DGoiko

14

Щоб завершити відповіді мого колеги, можливо, ви захочете звернути пильну увагу на:

  • статичні виклики або екземпляри (в одному випадку вам не потрібен примірник класу, в іншому, можливо, вам потрібно буде покластися на існуючий конструктор за замовчуванням, який може бути, а може і не бути)
  • загальнодоступний або непублічний виклик методу (для останнього вам потрібно викликати setAccessible за методом у блоці doPrivileged ; інші помилки пошуку не будуть задоволені )
  • інкапсуляція в ще один керований застосувальний виняток, якщо ви хочете повернути численні винятки з системи java (отже, CCException в коді нижче)

Ось старий код Java1.4, який враховує ці моменти:

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

Об'єкт повинен мати принаймні значення / значення.
Лова Чіттумурі

Це справді добре працювало для того, що мені було потрібно. У мене був клас, який вже був ініційований і просто потрібен, щоб вийти з нього метод. Додавання лову для винятків тут є хорошою ідеєю, але в іншому випадку це для мене прекрасно працювало. Я думаю, що мій спосіб уникнути нульових винятків полягав у використанні нульових значень, але я використовував дуже обмежений діапазон імен методів (буквально просто лічильник від 1 до 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

Якщо ви здійснюєте виклик кілька разів, ви можете використовувати нові методи ручки, введені в Java 7. Тут ми шукаємо ваш метод повернення рядка:

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
Майбутнім читачам; Якщо ви дбаєте про продуктивність, ви хочете використовувати, invokeExactколи зможете. Для цього підпис сайту виклику повинен точно відповідати типу ручки методу. Зазвичай потрібно трохи поворухнутися, щоб приступити до роботи. У цьому випадку вам потрібно буде передати перший параметр за допомогою: methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(0, Object.class));а потім викликати якString result = (String) methodHandle.invokeExact(obj);
Jorn Vernee

7

Це звучить як щось, що можна зробити з пакетом Java Reflection.

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

Зокрема за методами виклику за назвою:

імпорт 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

Індексація (швидше)

Ви можете використовувати FunctionalInterfaceдля збереження методів у контейнері, щоб їх індексувати. Ви можете використовувати контейнер масиву, щоб викликати їх за номерами або хешмапом, щоб викликати їх за рядками. Цим трюком ви можете проіндексувати свої методи, щоб швидше викликати їх .

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

Синтаксис лямбда

Ви також можете використовувати синтаксис лямбда:

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
Ця техніка здається набагато кращою, ніж рефлексія.
Джон О

Це справді набагато краще?
Димитрій Копріва

@DimitriKopriwa Індексація - це спосіб використання оперативної пам'яті замість розрахунків процесора. Для цілої індексації складність алгоритму становить O(1).
Амір Фо

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

SomeClassє класом і someVariableє змінною.


якщо someVariable справді є об'єктом, зателефонуйте someVariable.getClass (). Крім того, ви не можете викликати getMethod () з класом як єдиним аргументом. Жоден метод виклику з методом. Правильно: someVariable.getClass (). GetMethod ("coolMethod", parameterClasses) .invoke (аргументи);
Помаранчевий

5

Я роблю це так:

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

5

Зверніться до наступного коду.

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

Дякую....


Це не те, як ви порівнюєте Strings на Java. Ви повинні використовувати .equals метод. В іншому випадку ви просто порівнюєте, що вони є однаковими посиланнями на об'єкти, і ви насправді не переймаєтесь посиланнями на об'єкти - просто вміст рядка відповідає збігу. Ви також можете отримати метод за назвою за допомогою рефлексії, тож не знаєте, навіщо ви будете катати свій власний?
Ло-Тан

5

Ось ГОТОВІ ДО ВИКОРИСТАННЯ МЕТОДИ:

Щоб викликати метод без аргументів:

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

Використовуйте наведені вище методи, як показано нижче:

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

Вихід:

Метод А  
Спосіб B:  
	Парам1 - 1  
	Парам 2 - Тест

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

Ви повинні використовувати рефлексію - init об'єкт класу, потім метод цього класу, а потім викликати цей метод на об'єкт із необов'язковими параметрами. Не забудьте загортати наступний фрагмент у блок " спробу лову "

Сподіваюся, це допомагає!

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

Це добре для мене:

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

}

Вихід:

My input testparam

Я можу викликати метод, передавши його ім'я іншому методу (наприклад, головному).


1

використовуючи 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);
}

і ось як ви це використовуєте:

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

0

З jooR це просто:

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

Ось більш детальний приклад:

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

Це відбитки:

3, 12, 1


-10

для мене досить простим і нерозумним способом було б просто зробити такий метод виклику методу:

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

тоді, коли вам потрібно викликати метод, просто поставте щось подібне

//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
Якщо екземпляр уже відомий під час компіляції, чому б вам просто не зробити це className.getName().toString()? Ви пропускаєте всю точку роздумів.
BalusC

Як я вже говорив, непотрібне в цьому випадку, але якщо припустити, що ви завжди будете знати, що екземпляр - це погана звичка програмування.
SMayne

2
@SMayne: Я б запропонував видалити цю публікацію.
lpapp

Погане програмування скоріше буде компліментом у цій справі
pdenti
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.