Method类
获取Method信息
我们已经能通过Class
实例获取所有Field
对象,同样的,可以通过Class
实例获取所有Method
信息。Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)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
可能不允许对java
和javax
开头的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对象。可以使用回调的接口,这样不仅代码的执行速度更快,也更利于维护。