booky99
booky99

Reputation: 1456

Use Reflection to invoke a public method

I'm writing an API and I ran into an issue with using reflection. What I want to do is call a method from a custom object class and return the value to the spot called. The start of the call occurs in the MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ByteMe b = new ByteMe();

        ExampleObject object = new ExampleObject("Bob", 20, "indy", "male", "its bobby");
        try {
            b.examine(object);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

My example object class is meant to help make sure this API works

public class ExampleObject {

    private String Name;
    private int Age;
    private String Location;
    private String Sex;
    private String Description;

    /**
     * Empty Constructor
     */
    public ExampleObject() {}

    /**
     * Basic constructor with initializing data
     *
     * @param _Name String with the name of the user
     * @param _Age Integer with the age of the user
     * @param _Location String containing the curret city and state of the user
     * @param _Sex String Male, Female, Transgender, or Other
     * @param _Description String short blurb about the user
     */
    public ExampleObject(String _Name, int _Age, String _Location, String _Sex, String _Description)
    {
        this.setName(_Name);
        this.setAge(_Age);
        this.setLocation(_Location);
        this.setSex(_Sex);
        this.setDescription(_Description);
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public int getAge() {
        return Age;
    }

    public void setAge(int age) {
        Age = age;
    }

    public String getLocation() {
        return Location;
    }

    public void setLocation(String location) {
        Location = location;
    }

    public String getSex() {
        return Sex;
    }

    public void setSex(String sex) {
        Sex = sex;
    }

    public String getDescription() {
        return Description;
    }

    public void setDescription(String description) {
        Description = description;
    }
}

Now the main issue I have is when I make this call within another file called ByteMe.java:

public void examine(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        //Get the list of possible methods held in this object.
        Method[] methods = obj.getClass().getMethods();

        // iterate through them
        for (Method method : methods) {
            Log.d("" + this.getClass().getName(), "--------------------------");
            Log.d("" + this.getClass().getName(), "Method: " + method.getName());
            Log.d("" + this.getClass().getName(), "Return Type: " + method.getReturnType());
            Log.d("" + this.getClass().getName(), "Class: " + method.getClass());
            Log.d("" + this.getClass().getName(), "Declaring Class: " + method.getDeclaringClass());

            if(method.getReturnType().getName().contains("int")) {
                try {
                    Method m = method.getDeclaringClass().getMethod(method.getName(), Integer.TYPE);
                    int temp = (int) m.invoke(null, 0); //first argument is the object to invoke on, ignored if static method
                    Log.d("" + this.getClass().getName(),"temp value: " + temp);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            Log.d("" + this.getClass().getName(), "--------------------------");
        }
    }

This is an excerpt of my logcat:

11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Method: equals
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Return Type: boolean
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Class: class java.lang.reflect.Method
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Declaring Class: class java.lang.Object
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Method: getAge
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Return Type: int
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Class: class java.lang.reflect.Method
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: Declaring Class: class productions.widowmaker110.byteme.ExampleObject
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err: java.lang.NoSuchMethodException: getAge [int]
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.Class.getMethod(Class.java:664)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.Class.getMethod(Class.java:643)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at Library.ByteMe.examine(ByteMe.java:94)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at productions.widowmaker110.byteme.MainActivity.onCreate(MainActivity.java:21)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.Activity.performCreate(Activity.java:5958)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2364)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2474)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.access$800(ActivityThread.java:144)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1359)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.os.Looper.loop(Looper.java:155)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5696)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1028)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------

I looked up the NoSuchMethodFound error and most of stackoverflow said it was due to the method being private but my method is public. The invoke method in examine() should return 20 since thats the age of the object I made in the MainActivity. It just throws an exception though. help?

EDIT Alain O'Dea, thanks for the help. this is what i had to change to make it work.

 public void examine(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        //Get the list of possible methods held in this object.
        Method[] methods = obj.getClass().getMethods();

        // iterate through them
        for (Method method : methods) {
            Log.d("" + this.getClass().getName(), "--------------------------");
            Log.d("" + this.getClass().getName(), "Method: " + method.getName());
            Log.d("" + this.getClass().getName(), "Return Type: " + method.getReturnType());
            Log.d("" + this.getClass().getName(), "Class: " + method.getClass());
            Log.d("" + this.getClass().getName(), "Declaring Class: " + method.getDeclaringClass());

            if(method.getReturnType().getName().contains("int")) {
                try {
                    int temp = (int) method.invoke(obj);
                    Log.d("" + this.getClass().getName(),"temp value: " + temp);
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            Log.d("" + this.getClass().getName(), "--------------------------");
        }
    }

logcat

11-21 20:48:15.459 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: Method: getAge
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: Return Type: int
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: Class: class java.lang.reflect.Method
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: Declaring Class: class productions.widowmaker110.byteme.ExampleObject
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: temp value: 20
11-21 20:48:15.469 26949-26949/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------

Upvotes: 2

Views: 2840

Answers (2)

Alain O'Dea
Alain O'Dea

Reputation: 21716

You have this code:

int temp = (int) m.invoke(null, 0); //first argument is the object to invoke on, ignored if static method

And you get this stack trace as a result:

11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err: java.lang.NoSuchMethodException: getAge [int]
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.Class.getMethod(Class.java:664)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.Class.getMethod(Class.java:643)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at Library.ByteMe.examine(ByteMe.java:94)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at productions.widowmaker110.byteme.MainActivity.onCreate(MainActivity.java:21)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.Activity.performCreate(Activity.java:5958)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2364)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2474)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.access$800(ActivityThread.java:144)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1359)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.os.Looper.loop(Looper.java:155)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5696)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1028)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
11-21 20:08:11.704 18121-18121/productions.widowmaker110.byteme D/Library.ByteMe: --------------------------

Notably getAge(int) is a non-static (instance) method:

public int getAge() {
    return Age;
}

The problem triggering that exception stems from this code:

Method m = method.getDeclaringClass().getMethod(method.getName(), Integer.TYPE);

That would ask for a method int methodName (for example int getAge(int)) which doesn't exist triggering the NoSuchMethodException. This is a superflouous step since you can just call invoke on method instead.

That won't work alone though since the invoke has issues too. So you have to make the reflective call with the object instead of null. That isn't the full problem either. The method is int getAge() not int getAge(int). It doesn't take an int parameter. So you also need to remove the 0 parameter from invoke.

Here is the corrected line:

int temp = (int) method.invoke(obj); //first argument is the object to invoke on, ignored if static method

The documentation of Object Method.invoke(Object,Object...) is useful for reference:

public Object invoke(Object obj,
            Object... args)
              throws IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

Invokes the underlying method represented by this Method object, on the specified object with the specified parameters. Individual parameters are automatically unwrapped to match primitive formal parameters, and both primitive and reference parameters are subject to method invocation conversions as necessary.

If the underlying method is static, then the specified obj argument is ignored. It may be null.

If the number of formal parameters required by the underlying method is 0, the supplied args array may be of length 0 or null.

If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, Second Edition, section 15.12.4.4; in particular, overriding based on the runtime type of the target object will occur.

If the underlying method is static, the class that declared the method is initialized if it has not already been initialized.

If the method completes normally, the value it returns is returned to the caller of invoke; if the value has a primitive type, it is first appropriately wrapped in an object. However, if the value has the type of an array of a primitive type, the elements of the array are not wrapped in objects; in other words, an array of primitive type is returned. If the underlying method return type is void, the invocation returns null.

Parameters:

  • obj - the object the underlying method is invoked from
  • args - the arguments used for the method call

Returns:

  • the result of dispatching the method represented by this object on obj with parameters args

Throws:

  • IllegalAccessException - if this Method object is enforcing Java language access control and the underlying method is inaccessible.
  • IllegalArgumentException - if the method is an instance method and the specified object argument is not an instance of the class or interface declaring the underlying method (or of a subclass or implementor thereof); if the number of actual and formal parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion.
  • InvocationTargetException - if the underlying method throws an exception.
  • NullPointerException - if the specified object is null and the method is an instance method. ExceptionInInitializerError - if the initialization provoked by this method fails.

SOURCE: http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Method.html#invoke(java.lang.Object,%20java.lang.Object...)

Upvotes: 3

rharter
rharter

Reputation: 2495

If you look at the docs for the Class class, you'll see that getMethod(String name, Class...<?> parameterTypes) takes arguments that denote the method name ("getAge" in your case) and the parameter types (Integer). The class you're accessing has no method with that signature (public int getAge(int age)), hence the NoSuchMethodException. It's not lying to you.

Also, since you already have the methods to step through, why not just call m.invoke(obj) if it's the right one instead of querying the class for the method again? That might clean things up a bit.

Lastly, as your comment says, when you call invoke() you need to pass the object that you are invoking the method on, unless it's static (which it isn't). So you need to change that to m.invoke(obj).

Upvotes: 2

Related Questions