Как вызвать метод Java, если имя метода указано в виде строки?

684

Если у меня есть две переменные:

Object obj;
String methodName = "getName";

Не зная класса obj, как я могу вызвать метод, указанный methodNameна нем?

Вызываемый метод не имеет параметров и Stringвозвращаемого значения. Это геттер для Java-бина .

brasskazoo
источник
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 Reflection

Хенрик Пол
источник
2
Я был немного расстроен тем фактом, что Java использует стирание типов, но знание того, что, по крайней мере, в нем есть Reflection, снова поднимает мне настроение: D А теперь с лямбдами в Java 8 язык действительно набирает скорость благодаря современной разработке. Единственное, чего сейчас не хватает, - это встроенной поддержки методов получения и установки или свойств, известных в C #.
7hi4g0
120
Нечестно -1. Хенрик, вероятно, не защищает исключения из правил и ничего не написал для них, потому что он просто пытается продемонстрировать свое отражение.
рисовал
70
Плюс один для показа некоторых потенциальных исключений. Если бы я написал это, это было бы ... поймать (Исключение е) {...
mikbanUtah
1
Я получил "переменная не может быть инициализирована" для methodin 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 имеет тип Object и является объектом, для которого вы хотите вызвать метод
  • "method name" это имя метода, который вы хотите вызвать
  • parameterTypesимеет тип Class[]и объявляет параметры, которые принимает метод
  • paramsимеет тип Object[]и объявляет параметры, передаваемые методу
Оуэн
источник
Круто, я думаю, что вы правы с getDeclaredMethod (), это, вероятно, «безопаснее», чем getMethod () ..
brasskazoo
22
Неправильно. Да, getDeclaredMethod работает с закрытыми и защищенными методами. НО: он не работает с методами, определенными в суперклассах (унаследованные методы). Таким образом, это сильно зависит от того, что вы хотите сделать. Во многих случаях вы хотите, чтобы он работал независимо от класса, в котором определен метод.
Джрудольф
И куда мне положить файл "класса"? желательно объяснить для Eclipse IDE
Dr.jacky
@ Мистер Хайд на пути к классам.
Стейн де Витт
Что я должен поместить внутри и 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
Лучший ответ здесь. Полный и краткий
Рувим Ямес Авиньо Грута
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 рефлексии. Отражение бобов отражает относительно безопасный и обычный доступ.

Том Хотин - Tackline
источник
11
Я не согласен. Очень легко написать такой код, чтобы быть безопасным, и я сделал это на нескольких языках. Например, можно создать набор допустимых методов и разрешить вызов метода только в том случае, если его имя находится в наборе. Еще более безопасным (но все еще простым) было бы ограничивать каждый разрешенный метод определенным состоянием и не позволять вызывать метод, если поток / интерфейс / пользователь / что-либо не соответствует таким критериям.
JSON
Никогда не будь так категоричен в таких вопросах. Прямо сейчас я создаю простую программу, которая позволяет пользователю определять произвольные задачи над произвольными объектами с помощью веб-интерфейсов. Я знаю, что это действительно небезопасно, но правильное тестирование выполняется после получения конфигурации, и это позволяет непрограммисту легко конфигурировать задачи, а также дает программам возможность связывать пользовательские классы с общим кодом (то есть часть, которую я использую для отражения, чтобы они могли настроить, какие методы использовать через веб-интерфейс) без необходимости обновлять графический интерфейс.
DGoiko
14

Чтобы завершить ответы моего коллеги, вы можете обратить пристальное внимание на:

  • статические вызовы или вызовы экземпляров (в одном случае вам не нужен экземпляр класса, в другом - вам может потребоваться существующий конструктор по умолчанию, который может быть или не быть)
  • открытый или непубличный вызов метода (для последнего вам нужно вызвать setAccessible для метода в блоке doPrivileged , другие findbugs не будут удовлетворены )
  • инкапсуляция в еще одно управляемое аппликативное исключение, если вы хотите отбросить многочисленные системные исключения 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;
}
VonC
источник
12
Object obj;

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

method.invoke(obj, null);
chickeninabiscuit
источник
Объект должен иметь хотя бы значение / значения.
Лова Читтумури
Это сработало очень хорошо для того, что мне нужно. У меня был класс, который уже был создан, и мне просто нужно было извлечь из него метод. Добавление ловушек для исключений - хорошая идея, но в остальном это отлично сработало для меня. Я думаю, что мой способ избежать нулевых исключений состоял в том, чтобы использовать обнуляемые значения, но я использовал очень ограниченный диапазон имен методов (буквально счетчик от 1 до 4).
Джейн Уолдрип
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
источник
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);
     }
  }
}
zxcv
источник
6

Индексирование (быстрее)

Вы можете использовать FunctionalInterfaceдля сохранения методов в контейнере для их индексации. Вы можете использовать контейнер массива для вызова их по номерам или hashmap для вызова их по строкам. К этой уловке, вы можете индексировать ваши методы , чтобы вызвать их динамически быстрее .

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

Спасибо....

Рахул Каранкал
источник
Это не то, как вы сравниваете строки в 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);
    }
}

Вывод:

Метод А  
Метод Б:  
	Param1 - 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();
        }
     }
}
user8387971
источник
1

Вы должны использовать отражение - инициализировать объект класса, затем метод в этом классе, а затем вызывать этот метод для объекта с необязательными параметрами. Не забудьте обернуть следующий фрагмент в блок try-catch

Надеюсь, поможет!

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
источник
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

Я могу вызвать метод, передав его имя другому методу (например, main).

Лаксман Г
источник
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()); 
SMayne
источник
4
Если экземпляр уже известен во время компиляции, почему бы вам просто не узнать className.getName().toString()? Вы упускаете весь смысл размышления.
BalusC
Как я уже сказал, в этом случае нет необходимости, но при условии, что вы всегда будете знать, что это плохая привычка программирования.
SMayne
2
@SMayne: я бы предложил удалить этот пост.
lpapp
в этом случае плохое программирование скорее будет комплиментом
pdenti