反射之获取方法
1 import java.lang.reflect.*; 2 public class Demo12{ 3 4 /* 5 通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: 6 7 Method getMethod(name, Class...):获取某个public的Method(包括父类) 8 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) 9 Method[] getMethods():获取所有public的Method(包括父类) 10 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 11 */ 12 13 //reflection反射 14 public static void main(String[] args) throws Exception{ //记住要抛出异常 15 Class stdClass = Student.class; 16 //获取public方法getScore,参数为String 17 System.out.println(stdClass.getMethod("getScore",String.class)); 18 //获取继承的public方法getName,无参数 19 System.out.println(stdClass.getMethod("getName")); 20 //获取private方法getGrade,参数为int 21 System.out.println(stdClass.getDeclaredMethod("getGrade",int.class)); 22 23 } 24 } 25 26 class Student extends Person { 27 public int getScore(String type) { 28 return 99; 29 } 30 private int getGrade(int year) { 31 return 1; 32 } 33 } 34 35 class Person { 36 public String getName() { 37 return "Person"; 38 } 39 }
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
调用方法
当我们获取到一个Method
对象时,就可以对它进行调用。
如果用反射来调用substring
方法,参考以下代码:
1 import java.lang.reflect.*; 2 public class Demo12{ 3 4 /* 5 通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: 6 7 Method getMethod(name, Class...):获取某个public的Method(包括父类) 8 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) 9 Method[] getMethods():获取所有public的Method(包括父类) 10 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 11 */ 12 13 //reflection反射 14 public static void main(String[] args) throws Exception{ //记住要抛出异常 15 String s = "Hello world"; 16 //获取子串 17 System.out.println(s.substring(6)); //world 18 System.out.println("****************"); 19 //获取String对象的substring(int,int)方法,参数为int,int 20 Method m = s.getClass().getMethod("substring",int.class,int.class); 21 //查看一下 22 System.out.println(m); 23 //在s对象上调用该方法并获取结果 24 String r = (String)m.invoke(s,6,9); 25 System.out.println(r);//wor 26 } 27 }
注意到substring()
有两个重载方法,我们获取的是String substring(int,int)
这个方法
对Method
实例调用invoke
就相当于调用该方法,invoke
的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错
调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。我们以Integer.parseInt(String)
为例:
1 import java.lang.reflect.*; 2 public class Demo12{ 3 4 /* 5 通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: 6 7 Method getMethod(name, Class...):获取某个public的Method(包括父类) 8 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) 9 Method[] getMethods():获取所有public的Method(包括父类) 10 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 11 */ 12 13 //reflection反射 14 public static void main(String[] args) throws Exception{ //记住要抛出异常 15 //获取Integer的Class对象 16 Class cls = Integer.class; 17 //获取Integer的静态方法parseInt 18 Method m = cls.getMethod("parseInt",String.class); 19 // 调用该静态方法并获取结果: 20 Integer n = (Integer)m.invoke(null,"123"); 21 System.out.println(n); //123 22 } 23 }
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用:
1 import java.lang.reflect.*; 2 public class Demo12{ 3 4 /* 5 通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: 6 7 Method getMethod(name, Class...):获取某个public的Method(包括父类) 8 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) 9 Method[] getMethods():获取所有public的Method(包括父类) 10 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 11 */ 12 13 //reflection反射 14 public static void main(String[] args) throws Exception{ //记住要抛出异常 15 Person p = new Person(); 16 //获取Person的Class对象 17 Class cls = p.getClass(); 18 //获取setNamef方法的Method对象 19 Method m = cls.getDeclaredMethod("setName",String.class); 20 //private修饰的方法,需要设置开启访问权限 21 m.setAccessible(true); 22 //调用setName(String)方法 23 m.invoke(p,"2020年1月4日"); 24 //查看一下name字段值 25 System.out.println(p.name); 26 } 27 } 28 29 class Person { 30 String name; 31 private void setName(String name) { 32 this.name = name; 33 } 34 }
此外,setAccessible(true)
可能会失败。如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不允许对java
和javax
开头的package
的类调用setAccessible(true)
,这样可以保证JVM核心库的安全
多态
我们来考察这样一种情况:一个Person
类定义了hello()
方法,并且它的子类Student
也覆写了hello()
方法,那么下面程序,调用的方法到底是哪个?
1 import java.lang.reflect.*; 2 public class Demo12{ 3 4 /* 5 通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: 6 7 Method getMethod(name, Class...):获取某个public的Method(包括父类) 8 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) 9 Method[] getMethods():获取所有public的Method(包括父类) 10 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 11 */ 12 13 //reflection反射 14 public static void main(String[] args) throws Exception{ //记住要抛出异常 15 Person p = new Student(); 16 //获取Person的Class对象 17 Class cls = p.getClass(); 18 //获取setNamef方法的Method对象 19 Method m = cls.getMethod("hello"); 20 //调用hello方法,会打印什么呢? 21 m.invoke(p); 22 } 23 } 24 25 class Person { 26 public void hello() { 27 System.out.println("Person:hello"); 28 } 29 } 30 31 class Student extends Person { 32 public void hello() { 33 System.out.println("Student:hello"); 34 } 35 }
运行上述代码,发现打印出的是Student:hello
,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)
小结
Java的反射API提供的Method对象封装了方法的所有信息:
通过Class
实例的方法可以获取Method
实例:getMethod()
,getMethods()
,getDeclaredMethod()
,getDeclaredMethods()
;
通过Method
实例可以获取方法信息:getName()
,getReturnType()
,getParameterTypes()
,getModifiers()
;
通过Method
实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)
;
通过设置setAccessible(true)
来访问非public
方法;
通过反射调用方法时,仍然遵循多态原则。