Method类

获取Method信息

我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:"getScore"
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

下面是获取方法信息的程序:

private static void getMethodInfo (Class cl)
{
    Method[] methods = cl.getDeclaredMethods();

    for (Method method:methods)
    {
        String m = Modifier.toString(method.getModifiers());

        if (m.length() > 0) {
            m += " ";
        }

        System.out.print(m);
        System.out.print(method.getReturnType().getName() + " ");
        System.out.print(method.getName() + " ");
        System.out.print("(");

        Class[] paramTypes = method.getParameterTypes();

        for (int i = 0; i < paramTypes.length; i ++)
        {
            if (i > 0) {
                System.out.print(", ");
            }

            System.out.print(paramTypes[i].getName());
        }

        System.out.println(");");
    }
}

调用方法

package com.studyjava.demo;
import java.lang.reflect.Method;

public class Demo7
{
    public static void main (String[] args) throws Exception
    {
        String s = "This is an apple,isn't it?";
        String sub1 = s.substring(11, 16);

        Method method = String.class.getMethod("substring", int.class, int.class);
        String sub2 = (String) method.invoke(s, 11, 16);

        System.out.println(sub1 + sub2);
    }
}

调用静态方法

如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)为例:

package com.studyjava.demo;
import java.lang.reflect.Method;

public class Demo8
{
    public static void main (String[] args) throws Exception
    {
        Method m = Integer.class.getMethod("parseInt", String.class);
        int n = (Integer) m.invoke(null, "365");
        System.out.println(n);
    }
}

调用非public方法

和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用.

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对javajavax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);
    }
}

class Person {
    String name;
    private void setName(String name) {
        this.name = name;
    }
}

多态

我们来考察这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Person的hello方法:
        Method h = Person.class.getMethod("hello");
        // 对Student实例调用hello方法:
        h.invoke(new Student());
    }
}

class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}

class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}

运行上述代码,发现打印出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。上述的反射代码:

Method m = Person.class.getMethod("hello");
m.invoke(new Student());

实际上相当于:

Person p = new Student();
p.hello();

完成C函数指针功能

在c语言中,可以通过一个函数指针来调用任意一个函数。在java中,也可以通过反射完成同样功能(可以,但不建议。推荐使用接口或lambda表达式)

代码如下:

package com.studyjava.demo;

import java.lang.reflect.*;
import java.util.*;

public class Demo10
{
    public static void main (String[] args) throws Exception
    {
        // 将数组字符串进行排序,可以按字符串长度排,或按字典顺序比较两个字符串
        String[] strs = {
            "Hi,how are you?",
            "I'm fine,thanks.",
            "What about you?",
            "Very nice.",
            "Do you like play phone games?",
            "Yes,I do"
        };  

        Method byLength = Demo10.class.getMethod("compareByLen", String.class, String.class);
        Method byUVal = Demo10.class.getMethod("compareByUValue", String.class, String.class);

        String[] s1s = sortStr(Arrays.copyOf(strs, strs.length), byLength);
        String[] s2s = sortStr(Arrays.copyOf(strs, strs.length), byUVal);

        for (String s:s1s)
        {
            System.out.println(s);
        }

        System.out.println("=================");

        for (String s:s2s)
        {
            System.out.println(s);
        }
    }

    public static int compareByLen (String s1, String s2)
    {
        return s1.length() - s2.length();
    }

    public static int compareByUValue (String s1, String s2)
    {
        return s1.compareTo(s2);
    }

    private static String[] sortStr (String[] strs, Method sortType) throws Exception
    {
        for (int i = 0; i < strs.length - 1; i++)
        {
            for (int j = 0; j < strs.length - i - 1; j ++)
            {
                if ((int) sortType.invoke(null, strs[j], strs[j+1]) > 0 ) {
                    String tmp = strs[j];
                    strs[j] = strs[j+1];
                    strs[j+1] = tmp;
                }
            }
        }

        return strs;
    }   
}

利用method对象可以实现C语言中函数指针所能完成的所有操作。但是,这种编程风格不是很简便,而且非常容易出错。

另外,invoke的参数的返回值必须是Object类型。这就意味着必须来回进行多次强制类型转换。这样一样,编译器会丧失检查代码的机会,以至于等到测试阶段才会发现错误,而这个时候查找和修正错误会麻烦很多。不仅如此,使用发射获得方法指针的代码要比直接调用方法的代码慢的多。

因此,更好的方法是使用接口或lambda表达式。特别的:不要使用回调函数的Method对象。可以使用回调的接口,这样不仅代码的执行速度更快,也更利于维护。