Java 反射机制

动态语言与静态语言

动态语言

动态语言是一类在运行时可以改变其结构的语言:例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或是其它结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
主要的动态语言有:Object-c、C#、JavaScript、PHP、Python等

静态语言

与动态语言相比,运行时结构不可变的语言就是静态语言。例如Java、C、C++
Java不是动态语言,但是Java可以称为“准动态语言”。即Java有一定的动态性,可以利用反射机制来获取类似于动态语言的 特性,Java的动态性让编程的时候更加灵活。

Java反射机制概述

什么是反射

Java Reflection:Java反射是Java被视为动态语言的关键,反射机制运行程序在执行期借助于Reflection API 去的任何类内部的信息,并能直接操作任意对象的内部属性及方法。

  1. Class c = Class.forName("java.lang.String")

在加载完类后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射
image.png :::tips tip:反射可以获取到private修饰的成员变量和方法 :::

反射的应用

  • 在运行时判断任意一个对象所属类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时候处理注解
  • 生成动态代理

    Java反射的优缺点

    1、优点

    反射可以在不知道会运行哪一个类的情况下,获取到类的信息,创建对象以及操作对象。这其实很方便于拓展,所以反射会是框架设计的灵魂,因为框架在设计的时候,为了降低耦合度,肯定是需要考虑拓展等功能的,不能将类型写死,硬编码。
    降低耦合度,变得很灵活,在运行时去确定类型,绑定对象,体现了多态功能。

    2、缺点

    反射可以修改权限,比如上面访问到private这些方法和属性,这是会破坏封装性的,有安全隐患,有时候,还会破坏单例的设计。对性能有影响。使用反射基本上是一种解释操作,可以告诉JVM,希望做什么并且它满足什么样的要求,这类操作总是慢于直接执行相同的操作。也就是说new创建和对象,比反射性能更高。

    反射相关的主要API

  • java.lang.Class:代表一个类

  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

    反射性能对比

    编写代码来进行测试,使用反射的时候和不适用反射,在执行方法时的性能对比

    1. /**
    2. * 反射性能
    3. *
    4. * @author: Fcant
    5. */
    6. public class ReflectionPerformance {
    7. /**
    8. * 普通方式调用
    9. */
    10. public static void test01() {
    11. User user = new User();
    12. long startTime = System.currentTimeMillis();
    13. for (int i = 0; i < 1000000000; i++) {
    14. user.getName();
    15. }
    16. long endTime = System.currentTimeMillis();
    17. System.out.println("普通方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
    18. }
    19. /**
    20. * 反射方式调用
    21. */
    22. public static void test02() throws Exception {
    23. Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
    24. Method getName = clazz.getDeclaredMethod("getName", null);
    25. User user = (User) clazz.newInstance();
    26. long startTime = System.currentTimeMillis();
    27. for (int i = 0; i < 1000000000; i++) {
    28. getName.invoke(user, null);
    29. }
    30. long endTime = System.currentTimeMillis();
    31. System.out.println("反射方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
    32. }
    33. /**
    34. * 反射方式调用,关闭权限检查
    35. */
    36. public static void test03() throws Exception {
    37. Class clazz = Class.forName("com.moxi.interview.study.annotation.User");
    38. Method getName = clazz.getDeclaredMethod("getName", null);
    39. User user = (User) clazz.newInstance();
    40. long startTime = System.currentTimeMillis();
    41. getName.setAccessible(true);
    42. for (int i = 0; i < 1000000000; i++) {
    43. getName.invoke(user, null);
    44. }
    45. long endTime = System.currentTimeMillis();
    46. System.out.println("反射方式执行10亿次getName的时间:" + (endTime - startTime) + " ms");
    47. }
    48. public static void main(String[] args) throws Exception {
    49. test01();
    50. test02();
    51. test03();
    52. }
    53. }

    运行结果:

    1. 普通方式执行10亿次getName的时间:3 ms
    2. 反射方式执行10亿次getName的时间:2554 ms
    3. 反射方式执行10亿次getName的时间:1365 ms

    上面分别是执行了 10亿次 getName的方法,从里面可以看出,通过直接实例化对象后,调用getName耗时最短,同时关闭了权限检查后的比不关闭能提高一倍的性能。

    反射操作泛型

    Java采用泛型擦除机制来引入泛型,Java中的泛型仅仅是给编译器Java才使用的,确保数据的安全性和免去强制类型转换的问题,但是一旦编译完成后,所有的泛型有关的类型全部被擦除
    为了通过反射操作这些类型,Java新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一到Class类中的类型但是有何原始类型齐名的类型。

  • ParameterizedType:表示一种参数化类型,比如Collection

  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表一种通配符类型的表达式

下面通过代码来获取方法上的泛型,包括参数泛型,以及返回值泛型

  1. /**
  2. * 通过反射获取泛型
  3. *
  4. * @author: Fcant
  5. */
  6. public class GenericityDemo {
  7. public void test01(Map<String, User> map, List<User> list) {
  8. System.out.println("test01");
  9. }
  10. public Map<String, User> test02() {
  11. System.out.println("test02");
  12. return null;
  13. }
  14. public static void main(String[] args) throws Exception{
  15. Method method = GenericityDemo.class.getMethod("test01", Map.class, List.class);
  16. // 获取所有的泛型,也就是参数泛型
  17. Type[] genericParameterTypes = method.getGenericParameterTypes();
  18. // 遍历打印全部泛型
  19. for (Type genericParameterType : genericParameterTypes) {
  20. System.out.println(" # " +genericParameterType);
  21. if(genericParameterType instanceof ParameterizedType) {
  22. Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
  23. for (Type actualTypeArgument : actualTypeArguments) {
  24. System.out.println(actualTypeArgument);
  25. }
  26. }
  27. }
  28. // 获取返回值泛型
  29. Method method2 = GenericityDemo.class.getMethod("test02", null);
  30. Type returnGenericParameterTypes = method2.getGenericReturnType();
  31. // 遍历打印全部泛型
  32. if(returnGenericParameterTypes instanceof ParameterizedType) {
  33. Type[] actualTypeArguments = ((ParameterizedType) returnGenericParameterTypes).getActualTypeArguments();
  34. for (Type actualTypeArgument : actualTypeArguments) {
  35. System.out.println(actualTypeArgument);
  36. }
  37. }
  38. }
  39. }

得到的结果

  1. # java.util.Map<java.lang.String, com.moxi.interview.study.annotation.User>
  2. class java.lang.String
  3. class com.moxi.interview.study.annotation.User
  4. # java.util.List<com.moxi.interview.study.annotation.User>
  5. class com.moxi.interview.study.annotation.User
  6. ###################
  7. class java.lang.String
  8. class com.moxi.interview.study.annotation.User

反射操作注解

通过反射能够获取到 类、方法、字段等上的注解

  • getAnnotation
  • getAnnotations

    ORM对象关系映射

    ORM即为:Object relationship Mapping,对象关系映射

  • 类和表结构对应

  • 属性和字段对应
  • 对象和记录对应

image.png
下面使用代码,模拟ORM框架的简单使用

  1. /**
  2. * ORMDemo
  3. *
  4. * @author: Fcant
  5. */
  6. @TableKuang("db_student")
  7. class Student2 {
  8. @FieldKuang(columnName = "db_id", type="int", length = 10)
  9. private int id;
  10. @FieldKuang(columnName = "db_age", type="int", length = 10)
  11. private int age;
  12. @FieldKuang(columnName = "db_name", type="varchar", length = 10)
  13. private String name;
  14. public Student2() {
  15. }
  16. public Student2(int id, int age, String name) {
  17. this.id = id;
  18. this.age = age;
  19. this.name = name;
  20. }
  21. public int getId() {
  22. return id;
  23. }
  24. public void setId(int id) {
  25. this.id = id;
  26. }
  27. public int getAge() {
  28. return age;
  29. }
  30. public void setAge(int age) {
  31. this.age = age;
  32. }
  33. public String getName() {
  34. return name;
  35. }
  36. public void setName(String name) {
  37. this.name = name;
  38. }
  39. @Override
  40. public String toString() {
  41. return "Student2{" +
  42. "id=" + id +
  43. ", age=" + age +
  44. ", name='" + name + '\'' +
  45. '}';
  46. }
  47. }
  48. /**
  49. * 自定义注解:类名的注解
  50. */
  51. @Target(ElementType.TYPE)
  52. @Retention(RetentionPolicy.RUNTIME)
  53. @interface TableKuang {
  54. String value();
  55. }
  56. /**
  57. * 自定义注解:属性的注解
  58. */
  59. @Target(ElementType.FIELD)
  60. @Retention(RetentionPolicy.RUNTIME)
  61. @interface FieldKuang {
  62. String columnName();
  63. String type();
  64. int length() default 0;
  65. }
  66. public class ORMDemo {
  67. public static void main(String[] args) throws Exception{
  68. // 获取Student 的 Class对象
  69. Class c1 = Class.forName("com.moxi.interview.study.annotation.Student2");
  70. // 通过反射,获取到全部注解
  71. Annotation [] annotations = c1.getAnnotations();
  72. for (Annotation annotation : annotations) {
  73. System.out.println(annotation);
  74. }
  75. // 获取注解的value值
  76. TableKuang tableKuang = (TableKuang)c1.getAnnotation(TableKuang.class);
  77. String value = tableKuang.value();
  78. System.out.println(value);
  79. // 获得类指定的注解
  80. Field f = c1.getDeclaredField("name");
  81. FieldKuang fieldKuang = f.getAnnotation(FieldKuang.class);
  82. System.out.println(fieldKuang.columnName());
  83. System.out.println(fieldKuang.type());
  84. System.out.println(fieldKuang.length());
  85. }
  86. }

反射的具体使用

1、获取对象的包名以及类名

  1. package invocation;
  2. public class MyInvocation {
  3. public static void main(String[] args) {
  4. getClassNameTest();
  5. }
  6. public static void getClassNameTest(){
  7. MyInvocation myInvocation = new MyInvocation();
  8. System.out.println("class: " + myInvocation.getClass());
  9. System.out.println("simpleName: " + myInvocation.getClass().getSimpleName());
  10. System.out.println("name: " + myInvocation.getClass().getName());
  11. System.out.println("package: " +
  12. "" + myInvocation.getClass().getPackage());
  13. }
  14. }

运行结果:

  1. class: class invocation.MyInvocation
  2. simpleName: MyInvocation
  3. name: invocation.MyInvocation
  4. package: package invocation

由上面结果可以看到:

  1. getClass():打印会带着class+全类名
  2. getClass().getSimpleName():只会打印出类名
  3. getName():会打印全类名
  4. getClass().getPackage():打印出package+包名

getClass()获取到的是一个对象,getPackage()也是。

2、获取Class对象

在java中,一切皆对象。java中可以分为两种对象,实例对象和Class对象。这里说的获取Class对象,其实就是第二种,Class对象代表的是每个类在运行时的类型信息,指和类相关的信息。比如有一个Student类,用Student student = new Student()new一个对象出来,这个时候Student这个类的信息其实就是存放在一个对象中,这个对象就是Class类的对象,而student这个实例对象也会和Class对象关联起来。有三种方式可以获取一个类在运行时的Class对象,分别是

  • Class.forName("com.Student")
  • student.getClass()
  • Student.class

实例代码如下:

  1. package invocation;
  2. public class MyInvocation {
  3. public static void main(String[] args) {
  4. getClassTest();
  5. }
  6. public static void getClassTest(){
  7. Class<?> invocation1 = null;
  8. Class<?> invocation2 = null;
  9. Class<?> invocation3 = null;
  10. try {
  11. // 最常用的方法
  12. invocation1 = Class.forName("invocation.MyInvocation");
  13. }catch (Exception ex){
  14. ex.printStackTrace();
  15. }
  16. invocation2 = new MyInvocation().getClass();
  17. invocation3 = MyInvocation.class;
  18. System.out.println(invocation1);
  19. System.out.println(invocation2);
  20. System.out.println(invocation3);
  21. }
  22. }

执行的结果如下,三个结果一样:

  1. class invocation.MyInvocation
  2. class invocation.MyInvocation
  3. class invocation.MyInvocation

3、getInstance()获取指定类型的实例化对象

首先有一个Student类,后面都会沿用这个类,将不再重复。

  1. class Student{
  2. private int age;
  3. private String name;
  4. public Student() {
  5. }
  6. public Student(int age) {
  7. this.age = age;
  8. }
  9. public Student(String name) {
  10. this.name = name;
  11. }
  12. public Student(int age, String name) {
  13. this.age = age;
  14. this.name = name;
  15. }
  16. public int getAge() {
  17. return age;
  18. }
  19. public void setAge(int age) {
  20. this.age = age;
  21. }
  22. public String getName() {
  23. return name;
  24. }
  25. public void setName(String name) {
  26. this.name = name;
  27. }
  28. @Override
  29. public String toString() {
  30. return "Student{" +
  31. "age=" + age +
  32. ", name='" + name + '\'' +
  33. '}';
  34. }
  35. }

可以使用getInstance()方法构造出一个Student的对象:

  1. public static void getInstanceTest() {
  2. try {
  3. Class<?> stduentInvocation = Class.forName("invocation.Student");
  4. Student student = (Student) stduentInvocation.newInstance();
  5. student.setAge(9);
  6. student.setName("Hahs");
  7. System.out.println(student);
  8. }catch (Exception ex){
  9. ex.printStackTrace();
  10. }
  11. }

输出结果如下:

  1. Student{age=9, name='Hahs'}

但是如果取消不写Student的无参构造方法呢?就会出现下面的报错:

  1. java.lang.InstantiationException: invocation.Student
  2. at java.lang.Class.newInstance(Class.java:427)
  3. at invocation.MyInvocation.getInstanceTest(MyInvocation.java:40)
  4. at invocation.MyInvocation.main(MyInvocation.java:8)
  5. Caused by: java.lang.NoSuchMethodException: invocation.Student.<init>()
  6. at java.lang.Class.getConstructor0(Class.java:3082)
  7. at java.lang.Class.newInstance(Class.java:412)
  8. ... 2 more

这是因为重写了构造方法,而且是有参构造方法,如果不写构造方法,那么每个类都会默认有无参构造方法,重写了就不会有无参构造方法了,所以调用newInstance()的时候,会报没有这个方法的错误。值得注意的是,newInstance()是一个无参构造方法。

4、通过构造函数对象实例化对象

除了newInstance()方法之外,其实还可以通过构造函数对象获取实例化对象,怎么理解?这里只构造函数对象,而不是构造函数,也就是构造函数其实就是一个对象,先获取构造函数对象,当然也可以使用来实例化对象。
可以先获取一个类的所有的构造方法,然后遍历输出:

  1. public static void testConstruct(){
  2. try {
  3. Class<?> stduentInvocation = Class.forName("invocation.Student");
  4. Constructor<?> cons[] = stduentInvocation.getConstructors();
  5. for(int i=0;i<cons.length;i++){
  6. System.out.println(cons[i]);
  7. }
  8. }catch (Exception ex){
  9. ex.printStackTrace();
  10. }
  11. }

输出结果:

  1. public invocation.Student(int,java.lang.String)
  2. public invocation.Student(java.lang.String)
  3. public invocation.Student(int)
  4. public invocation.Student()

取出一个构造函数可以获取到它的各种信息,包括参数,参数个数,类型等等:

  1. public static void constructGetInstance() {
  2. try {
  3. Class<?> stduentInvocation = Class.forName("invocation.Student");
  4. Constructor<?> cons[] = stduentInvocation.getConstructors();
  5. Constructor constructors = cons[0];
  6. System.out.println("name: " + constructors.getName());
  7. System.out.println("modifier: " + constructors.getModifiers());
  8. System.out.println("parameterCount: " + constructors.getParameterCount());
  9. System.out.println("构造参数类型如下:");
  10. for (int i = 0; i < constructors.getParameterTypes().length; i++) {
  11. System.out.println(constructors.getParameterTypes()[i].getName());
  12. }
  13. } catch (Exception ex) {
  14. ex.printStackTrace();
  15. }
  16. }

输出结果,modifier是权限修饰符,1表示为public,可以知道获取到的构造函数是两个参数的,第一个是int,第二个是String类型,看来获取出来的顺序并不一定是书写代码的顺序。

  1. name: invocation.Student
  2. modifier: 1
  3. parameterCount: 2
  4. 构造参数类型如下:
  5. int
  6. java.lang.String

既然可以获取到构造方法这个对象了,那么可不可以通过它去构造一个对象呢?
答案肯定是可以!!!下面用不同的构造函数来创建对象:

  1. public static void constructGetInstanceTest() {
  2. try {
  3. Class<?> stduentInvocation = Class.forName("invocation.Student");
  4. Constructor<?> cons[] = stduentInvocation.getConstructors();
  5. // 一共定义了4个构造器
  6. Student student1 = (Student) cons[0].newInstance(9,"Sam");
  7. Student student2 = (Student) cons[1].newInstance("Sam");
  8. Student student3 = (Student) cons[2].newInstance(9);
  9. Student student4 = (Student) cons[3].newInstance();
  10. System.out.println(student1);
  11. System.out.println(student2);
  12. System.out.println(student3);
  13. System.out.println(student4);
  14. } catch (Exception ex) {
  15. ex.printStackTrace();
  16. }
  17. }

输出如下:

  1. Student{age=9, name='Sam'}
  2. Student{age=0, name='Sam'}
  3. Student{age=9, name='null'}
  4. Student{age=0, name='null'}

构造器的顺序是必须一一对应的,要不会报一下的参数不匹配的错误:

  1. java.lang.IllegalArgumentException: argument type mismatch
  2. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  3. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  4. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  5. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  6. at invocation.MyInvocation.constructGetInstanceTest(MyInvocation.java:85)
  7. at invocation.MyInvocation.main(MyInvocation.java:8)

5、获取类继承的接口

通过反射可以获取接口的方法,如果某个类实现了接口的方法,同样可以做到通过类名创建对象调用到接口的方法。
首先定义两个接口,一个InSchool:

  1. public interface InSchool {
  2. public void attendClasses();
  3. }

一个AtHome:

  1. public interface AtHome {
  2. public void doHomeWork();
  3. }

创建一个实现两个接口的类Student.java

  1. public class Student implements AtHome, InSchool {
  2. public void doHomeWork() {
  3. System.out.println("I am a student,I am doing homework at home");
  4. }
  5. public void attendClasses() {
  6. System.out.println("I am a student,I am attend class in school");
  7. }
  8. }

测试代码如下:

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> studentClass = Class.forName("invocation.Student");
  4. Class<?>[] interfaces = studentClass.getInterfaces();
  5. for (Class c : interfaces) {
  6. // 获取接口
  7. System.out.println(c);
  8. // 获取接口里面的方法
  9. Method[] methods = c.getMethods();
  10. // 遍历接口的方法
  11. for (Method method : methods) {
  12. // 通过反射创建对象
  13. Student student = (Student) studentClass.newInstance();
  14. // 通过反射调用方法
  15. method.invoke(student, null);
  16. }
  17. }
  18. }
  19. }

结果如下:

  1. interface invocation.AtHome
  2. I am a student,I am doing homework at home
  3. interface invocation.InSchool
  4. I am a student,I am attend class in school

可以看出其实可以获取到接口的数组,并且里面的顺序是继承的顺序,通过接口的Class对象,可以获取到接口的方法,然后通过方法反射调用实现类的方法,因为这是一个无参数的方法,所以只需要传null即可。

6、获取父类相关信息

主要是使用getSuperclass()方法获取父类,当然也可以获取父类的方法,执行父类的方法,首先创建一个Animal.java:

  1. public class Animal {
  2. public void doSomething(){
  3. System.out.println("animal do something");
  4. }
  5. }

Dog.java继承于Animal.java

  1. public class Dog extends Animal{
  2. public void doSomething(){
  3. System.out.println("Dog do something");
  4. }
  5. }

可以通过反射创建Dog对象,获取其父类Animal以及创建对象,当然也可以获取Animal的默认父类Object

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> dogClass = Class.forName("invocation02.Dog");
  4. System.out.println(dogClass);
  5. invoke(dogClass);
  6. Class<?> animalClass = dogClass.getSuperclass();
  7. System.out.println(animalClass);
  8. invoke(animalClass);
  9. Class<?> objectClass = animalClass.getSuperclass();
  10. System.out.println(objectClass);
  11. invoke(objectClass);
  12. }
  13. public static void invoke(Class<?> myClass) throws Exception {
  14. Method[] methods = myClass.getMethods();
  15. // 遍历接口的方法
  16. for (Method method : methods) {
  17. if (method.getName().equalsIgnoreCase("doSomething")) {
  18. // 通过反射调用方法
  19. method.invoke(myClass.newInstance(), null);
  20. }
  21. }
  22. }
  23. }

输入如下:

  1. class invocation02.Dog
  2. Dog do something
  3. class invocation02.Animal
  4. animal do something
  5. class java.lang.Object

7、获取当前类的公有属性和私有属性以及更新

创建一个Person.java,里面有静态变量,非静态变量,以及publicprotected,private不同修饰的属性。

  1. public class Person {
  2. public static String type ;
  3. private static String subType ;
  4. // 名字(公开)
  5. public String name;
  6. protected String gender;
  7. private String address;
  8. @Override
  9. public String toString() {
  10. return "Person{" +
  11. "name='" + name + '\'' +
  12. ", address='" + address + '\'' +
  13. '}';
  14. }
  15. }

使用getFields()可以获取到public的属性,包括static属性,使用getDeclaredFields()可以获取所有声明的属性,不管是publicprotected,private不同修饰的属性。
修改public属性,只需要field.set(object,value)即可,但是private属性不能直接set,否则会报以下的错误。

  1. Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
  2. at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  3. at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
  4. at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  5. at java.lang.reflect.Field.set(Field.java:761)
  6. at invocation03.Tests.main(Tests.java:21)

private默认是不允许外界操作其值的,这里可以使用field.setAccessible(true);,相当于打开了操作的权限。
static的属性修改和非static的一样,但是怎么获取呢?如果是public修饰的,可以直接用类名获取到,如果是private修饰的,那么需要使用filed.get(object),这个方法其实对上面说的所有的属性都可以的。测试代码如下

  1. public class Tests {
  2. public static void main(String[] args) throws Exception{
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Field[] fields = personClass.getFields();
  5. // 获取公开的属性
  6. for(Field field:fields){
  7. System.out.println(field);
  8. }
  9. System.out.println("=================");
  10. // 获取所有声明的属性
  11. Field[] declaredFields = personClass.getDeclaredFields();
  12. for(Field field:declaredFields){
  13. System.out.println(field);
  14. }
  15. System.out.println("=================");
  16. Person person = (Person) personClass.newInstance();
  17. person.name = "Sam";
  18. System.out.println(person);
  19. // 修改public属性
  20. Field fieldName = personClass.getDeclaredField("name");
  21. fieldName.set(person,"Jone");
  22. // 修改private属性
  23. Field addressName = personClass.getDeclaredField("address");
  24. // 需要修改权限
  25. addressName.setAccessible(true);
  26. addressName.set(person,"东风路47号");
  27. System.out.println(person);
  28. // 修改static 静态public属性
  29. Field typeName = personClass.getDeclaredField("type");
  30. typeName.set(person,"人类");
  31. System.out.println(Person.type);
  32. // 修改静态 private属性
  33. Field subType = personClass.getDeclaredField("subType");
  34. subType.setAccessible(true);
  35. subType.set(person,"黄种人");
  36. System.out.println(subType.get(person));
  37. }
  38. }

结果:

  1. public static java.lang.String invocation03.Person.type
  2. public java.lang.String invocation03.Person.name
  3. =================
  4. public static java.lang.String invocation03.Person.type
  5. private static java.lang.String invocation03.Person.subType
  6. public java.lang.String invocation03.Person.name
  7. protected java.lang.String invocation03.Person.gender
  8. private java.lang.String invocation03.Person.address
  9. =================
  10. Person{name='Sam', address='null'}
  11. Person{name='Jone' , address='东风路47号'}
  12. 人类
  13. 黄种人

从结果可以看出,不管是public,还是protectedprivate修饰的,都可以通过反射对其进行查询和修改,不管是静态变量还是非静态变量。getDeclaredField()可以获取到所有声明的属性,而getFields()则只能获取到public的属性。对于非public的属性,需要修改其权限才能访问和修改:field.setAccessible(true)
获取属性值需要使用field.get(object),值得注意的是:每个属性,其本身就是对象

8、获取以及调用类的公有/私有方法

既然可以获取到公有属性和私有属性,那么执行公有方法和私有方法应该都不是什么问题?
先定义一个类,包含各种修饰符,以及是否包含参数,是否为静态方法,Person.java:

  1. public class Person {
  2. // 非静态公有无参数
  3. public void read(){
  4. System.out.println("reading...");
  5. }
  6. // 非静态公有无参数有返回
  7. public String getName(){
  8. return "Sam";
  9. }
  10. // 非静态公有带参数
  11. public int readABookPercent(String name){
  12. System.out.println("read "+name);
  13. return 80;
  14. }
  15. // 私有有返回值
  16. private String getAddress(){
  17. return "东方路";
  18. }
  19. // 公有静态无参数无返回值
  20. public static void staticMethod(){
  21. System.out.println("static public method");
  22. }
  23. // 公有静态有参数
  24. public static void staticMethodWithArgs(String args){
  25. System.out.println("static public method:"+args);
  26. }
  27. // 私有静态方法
  28. private static void staticPrivateMethod(){
  29. System.out.println("static private method");
  30. }
  31. }

首先获取里面所有的方法:

  1. public class Tests {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Method[] methods = personClass.getMethods();
  5. for (Method method : methods) {
  6. System.out.println(method);
  7. }
  8. System.out.println("=============================================");
  9. Method[] declaredMethods = personClass.getDeclaredMethods();
  10. for (Method method : declaredMethods) {
  11. System.out.println(method);
  12. }
  13. }
  14. }

结果如下:

  1. publicjava.lang.String invocation03.Person.getName( )
  2. public void invocation03.Person.read()
  3. public int invocation03.Person.readABookPercent(java.lang.String)
  4. public static void invocation03.Person.staticMethod()
  5. public static void invocation03.Person.staticMethodwithArgs()
  6. public final void java.lang.Object.wait(long, int) throws java.lang.InterruptedException
  7. public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
  8. public final void java.lang.Object.wait() throws java.lang.InterruptedException
  9. public boolean java.lang.Object.equals(java.lang.Object)
  10. public java.lang.String java.lang.Object.toString()
  11. public native int java.lang.Object.hashCode()
  12. public final native java.lang.Class java.lang.Object.getClass()
  13. public final native void java.lang.Object.notify()
  14. public final native void java.lang.Object.notifyAll()
  15. =============================================
  16. private java.lang.String invocation03.Person.getAddress()
  17. public java.lang.String invocation03.Person.getName()
  18. public void invocation03.Person.read()
  19. public int invocation03.Person.readABookPercent(java.lang.String)
  20. public static void invocation03.Person.staticMethod( )
  21. public static void invocation03.Person.staticMethodwithArgs()
  22. private static void invocation03.Person.staticPrivateMethod()

可以发现getMethods()确实可以获取所有的公有的方法,但是有一个问题,就是他会把父类的也获取到,也就是上面图片绿色框里面的,所有的类默认都继承了Object类,所以它把Object的那些方法都获取到了。而getDeclaredMethods确实可以获取到公有和私有的方法,不管是静态还是非静态,但是它是获取不到父类的方法的。
那如果想调用方法呢?先试试调用非静态方法:

  1. public class Tests {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Person person = (Person) personClass.newInstance();
  5. Method[] declaredMethods = personClass.getDeclaredMethods();
  6. for (Method method : declaredMethods) {
  7. if(method.getName().equalsIgnoreCase("read")){
  8. method.invoke(person,null);
  9. System.out.println("===================");
  10. }else if(method.getName().equalsIgnoreCase("getName")){
  11. System.out.println(method.invoke(person,null));
  12. System.out.println("===================");
  13. }else if(method.getName().equalsIgnoreCase("readABookPercent")){
  14. System.out.println(method.invoke(person,"Sam"));
  15. System.out.println("===================");
  16. }
  17. }
  18. }
  19. }

结果如下,可以看出method.invoke(person,null);是调用无参数的方法,而method.invoke(person,"Sam")则是调用有参数的方法,要是有更多参数,也只需要在里面多加一个参数即可,返回值也同样可以获取到。

  1. read Sam
  2. 80
  3. ===================
  4. Sam
  5. ===================
  6. reading...
  7. ===================

那么private方法呢?照着来试试

  1. public class Tests {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Person person = (Person) personClass.newInstance();
  5. Method[] declaredMethods = personClass.getDeclaredMethods();
  6. for (Method method : declaredMethods) {
  7. if(method.getName().equalsIgnoreCase("getAddress")){
  8. method.invoke(person,null);
  9. }
  10. }
  11. }
  12. }

结果报错了:

  1. Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
  2. at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  3. at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
  4. at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  5. at java.lang.reflect.Method.invoke(Method.java:491)
  6. at invocation03.Tests.main(Tests.java:13)

一看就是没有权限,加上下面的代码就可以解决

  1. method.setAccessible(true);

那么问题来了,上面说的都是非静态的,想要调用静态的方法。当然用上面的方法,对象也可以直接调用到类的方法的:

  1. static public method
  2. null
  3. static public method:myArgs
  4. null
  5. static private method
  6. null

一点问题都没有,输出结果有几个null,因为这函数是无返回值的。
如果不想用遍历方法的方式,再去判断怎么办?能不能直接获取到想要的方法?那答案肯定是可以的。

  1. public class Tests {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Person person = (Person) personClass.newInstance();
  5. Method method = personClass.getMethod("readABookPercent", String.class);
  6. method.invoke(person, "唐诗三百首");
  7. }
  8. }

结果和上面调用的完全一样,要是这个方法没有参数呢?那就给一个null就可以了。或者不给也可以。

  1. public class Tests {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> personClass = Class.forName("invocation03.Person");
  4. Person person = (Person) personClass.newInstance();
  5. Method method = personClass.getMethod("getName",null);
  6. System.out.println(method.invoke(person));
  7. }
  8. }