专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java反射机制详解:如何通过反射操作构造方法、方法和字段

今天还是用通俗易懂的大白话来写点我自己的理解和总结,今天要讲的是Java中比较重要的一个知识点:反射。看完如果有什么疑问的地方,可以留言讨论,也可以加我微信。我坚信,真正能让大家学到东西的文章才是好文章,这也是我最初决定写文章最主要的目标和动力,希望这篇文章对你有所帮助。ok,开始我们今天的内容。

先思考一个问题

在讲反射之前,先思考一个问题,java中如何创建一个对象,有哪几种方式?

Java中创建对象大概有这几种方式:

  • 使用new关键字:这是我们最常见的也是最简单的创建对象的方式
  • 使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去
  • 使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象

上边是Java中常见的创建对象的三种方式,其实除了上边的三种还有另外一种方式,就是接下来我们要讨论的“反射”。

反射的概述

什么是反射?按照我自己的总结和理解,反射其实就是把Java类中的各个组成部分进行解剖,并把这些解剖后得到的各个组成部分,映射成一个个的Java对象,拿到这些对象后可以做一些事情。

既然说反射是解剖Java类中的各个组成部分,所以说咱们得知道一个类中有哪些部分?一个类大致是由构造方法、方法、成员变量(字段)等信息组成的,利用反射拿到一个类的class字节码文件后,咱们可以把构造方法、方法、成员变量这些组成部分映射成一个个对象,那拿到这些映射的对象后,能干啥呢?

  • 拿到映射后的构造方法,可以用它来生成对象;
  • 拿到映射后的方法,可以调用它来执行对应的方法;
  • 拿到映射后的字段,可以用它来获取或改变对应字段的值;

反射能干什么

说完反射的概念后,咱们说一下反射能干什么?一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码,反射在日常的开发中用到的不多,但是咱们还必须搞懂它,因为搞懂了反射以后,可以帮助咱们理解框架的一些原理。所以说有一句很经典的话:反射是框架设计的灵魂。等下说完会讲一个简单的例子,来体验一下框架里大概是怎么用反射的。

怎么得到想反射的类?

刚才已经说过,反射是对一个类进行解剖,那想解剖一个东西,前提肯定是你得先得到这个东西,那么怎么得到咱们想解剖的类呢?

首先大家要明白一点,咱们写的代码是存储在后缀名是 .java的文件里的,但是它会被编译,最终真正去执行的是编译后的 .class文件。Java是面向对象的语言,一切皆对象,所以java认为这些编译后的class文件,也是一个个的对象,Java也给这事物抽象成了一种类,这个类对应的就是JDK里的Class类,大家可以去JDK的AIP文档里看一下这个类。

\"link-3\"

所以拿到这个类后,就相当于拿到了咱们想解剖的类,那怎么拿到这个类?接着看下文档,如下图

\"link-4\"

看API文档后,里边有一个 forName的方法,而且它是一个静态的方法,这样咱们直接调用它提供的这个静态方法就可以得到想反射的类了,比如用以下的方法

\"link-5\"

可以看到 Class.forName("com.cj.test.Person") 这个静态方法里接收的是一个字符串。字符串的话,我们就可以写在配置文件里,然后利用反射生成我们需要的对象,这才是我们想要的,很多框架里都有类似的配置。

解剖类

通过上边讲的方法就可以拿到想解剖的类了,接下来就可以对类进行反射解剖了。我们知道一个类里一般有构造函数、方法、成员变量(字段/属性)这三部分组成的。通过翻阅API文档,可以看到,Class这个类提供了如下常用方法:

public Constructor getConstructor(Class…parameterTypes)public Method getMethod(String name,Class… parameterTypes)public Field getField(String name)
public Constructor getDeclaredConstructor(Class…parameterTypes)public Method getDeclaredMethod(String name,Class… parameterTypes)public Field getDeclaredField(String name)

这些方法分别用于帮咱们从类中解剖出构造函数、方法和成员变量(属性)。然后把解剖出来的部分,分别映射成对应的Constructor、Method、Field对象。

  • 拿到映射后的Constructor对象后,可以用它来生成对象;
  • 拿到映射后的Method对象后,可以调用它来执行对应的方法;
  • 拿到映射后的Field对象后,可以用它来获取或改变对应字段的值;

好,下面来分别讲一下这三部分具体的怎么操作

反射类中的构造方法

1、反射无参的构造函数

\"link-6\"\"link-7\"

可以看到默认的无参构造方法执行了,从上边的例子看出,要想进行反射操作,首先第一步就是得到类的字节码,所以简单说一下得到类的字节码的几种方式:

  • Class.forName("com.cj.test.Person"); 这就是上边我们用的方式
  • 对象.getClass();
  • 类名.class;

下图中对应了上边说的三种方式

\"link-8\"

2、反射“一个参数”的构造函数

\"link-9\"

3、反射“多个参数”的构造函数

\"link-10\"

4、反射“私有”的构造函数

\"link-11\"

注意:在反射私有的构造函数时,用普通的clazz.getConstructor()会报错,因为它是私有的,所以JDK提供了专门反射私有构造函数的方法 clazz.getDeclaredConstructor(int.class)和c.setAccessible(true)。

5、反射得到类中所有的构造函数

\"link-12\"

反射类中的方法

反射类中的方法,我就不再一一展开截图讲了,我直接把对应的代码全部放上来,有对应的无参的方法、一个参数的方法、多个参数的方法、私有的方法等等各种情况里边有详细的介绍,如下

package com.cj.test;
import java.util.Date;
public class Person {
       public Person(){     System.out.println(\"默认的无参构造方法执行了\"); }
       public Person(String name){      System.out.println(\"姓名:\"+name); }
       public Person(String name,int age){      System.out.println(name+\"=\"+age);   }
       private Person(int age){     System.out.println(\"年龄:\"+age);  }
       public void m1() {       System.out.println(\"m1\");   }
       public void m2(String name) {        System.out.println(name);   }
       public String m3(String name,int age) {      System.out.println(name+\":\"+age);               return \"aaa\";   }
       private void m4(Date d) {        System.out.println(d);  }
       public static void m5() {        System.out.println(\"m5\");   }
       public static void m6(String[] strs) {       System.out.println(strs.length);    }
        public static void main(String[] args) {        System.out.println(\"main\"); }
}
package com.cj.test;
import java.lang.reflect.Method;import java.util.Date;import org.junit.Test;
public class Demo2 {
      @Test//public void m1()      public void test1() throws Exception{        Class clazz = Class.forName(\"com.cj.test.Person\");        Person p = (Person)clazz.newInstance();        Method m = clazz.getMethod(\"m1\", null);        m.invoke(p, null);      }
      @Test//public void m2(String name)      public void test2() throws Exception{         Class clazz = Person.class;         Person p = (Person) clazz.newInstance();         Method m = clazz.getMethod(\"m2\", String.class);         m.invoke(p, \"张三\");      }
      @Test//public String m3(String name,int age)      public void test3() throws Exception{         Class clazz = Person.class;         Person p = (Person) clazz.newInstance();         Method m = clazz.getMethod(\"m3\", String.class,int.class);         String returnValue = (String)m.invoke(p, \"张三\",23);         System.out.println(returnValue);      }
      @Test//private void m4(Date d)      public void test4() throws Exception{         Class clazz = Person.class;         Person p = (Person) clazz.newInstance();         Method m = clazz.getDeclaredMethod(\"m4\", Date.class);         m.setAccessible(true);         m.invoke(p,new Date());      }
       @Test//public static void m5()       public void test5() throws Exception{         Class clazz = Person.class;         Method m = clazz.getMethod(\"m5\", null);         m.invoke(null,null);       }
       @Test//private static void m6(String[] strs)       public void test6() throws Exception{         Class clazz = Person.class;         Method m = clazz.getDeclaredMethod(\"m6\",String[].class);         m.setAccessible(true);         m.invoke(null,(Object)new String[]{\"a\",\"b\"});       }
       @Test       public void test7() throws Exception{          Class clazz = Person.class;          Method m = clazz.getMethod(\"main\",String[].class);          m.invoke(null,new Object[]{new String[]{\"a\",\"b\"}});  }}

*****注意:可以看到上边代码里反射test6和test7方法,去执行invoke方法时,里边传的参数和其他的有点不一样。

你如果按照之前的方式用mainMethod.invoke(null,new String[]{“xxx”})这种方式去进行反射的话,执行的时候它会报参数个数不匹配的错误。这是因为jdk1.5之后出了可变参数,jdk1.4和jdk1.5处理invoke方法有区别的,分别如下:

1.4:public Object invoke(Object obj,Object[] args)1.5:public Object invoke(Object obj,Object…args)

由于JDK1.4和1.5对invoke方法的处理有区别,所以在反射类似于main(String[] args) 这种参数是数组的方法时需要特殊处理。

public static void main(String[] args),通过反射方式来调用参数是数组的这种方法时,按jdk1.5的语法,整个数组是一个参数,这是没问题的;但是按jdk1.4的语法,它会认为数组中的每个元素都是一个对应的参数。那当遇到这种当把一个数组作为参数传递给invoke方法时,java编的编译器到底会按照哪种语法进行处理呢?

jdk1.5肯定要向下兼容jdk1.4的语法,所以最后是按照jdk1.4的语法进行处理的,也就是把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”})这样来操作,因为编译器会把它用jdk1.4的语法进行解释,而不把它当作jdk1.5的语法解释,因此你用mainMethod.invoke(null,new String[]{“xxx”})这种方式调用,就会报参数个数不匹配的错误。

上述问题的解决方法:

第一种方式:mainMethod.invoke(null,(Object)new String[]{"xxx"});这种方式是你在这个数组前加个强转的操作,相当于你强制的告诉编译器你传的参数是一个整体的对象,而不是数组。所以此时就算是按照1.4的语法,编译时也不把参数当作数组看待,也就不会数组打散成若干个参数了,所以问题搞定。上边代码里的test6()这个方法,我用的就是这种方式来解决的。

第二种方式:mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});这种方式,由于你传的是一个数组的参数,所以为了向下兼容1.4的语法,javac遇到数组会给你拆开成多个参数,但是由于咱们new的这个Object[ ] 数组里只有一个元素值,所以就算它拆也没关系,它拆完了得到的还是一个元素。上边代码里的test7()这个方法,我用的就是这种方式来解决的。

对上边的描述进行一下总结:在反射参数是一个数组的这种方法时,考虑到向下兼容问题,会按照JDK1.4的语法来对待,JVM会把传递的数组参数拆开,拆开就会报参数的个数不匹配的错误。解决办法:防止JVM拆开你的数组或者让它拆完得到的还是整体的一个参数。

方式一:直接强制的告诉编译器把数组看做是一个整体的Object对象,你不要给我拆开。

方式二:重新new一个Object数组,这个new的数组里的数据作为唯一的元素存在,拆完得到的还是一个整体。

反射类中的属性字段

package com.cj.test;
import java.util.Date;
public class Person {
      public String name=\"李四\";      private int age = 18;      public static Date time;
      public int getAge() {        return age;      }
      public Person(){         System.out.println(\"默认的无参构造方法执行了\");      }
      public Person(String name){         System.out.println(\"姓名:\"+name);      }
       public Person(String name,int age){      System.out.println(name+\"=\"+age);   }
       private Person(int age){     System.out.println(\"年龄:\"+age);  }
       public void m1() {       System.out.println(\"m1\");   }
       public void m2(String name) {        System.out.println(name);   }
       public String m3(String name,int age) {      System.out.println(name+\":\"+age);               return \"aaa\";   }
       private void m4(Date d) {        System.out.println(d);  }
       public static void m5() {        System.out.println(\"m5\");   }
       public static void m6(String[] strs) {       System.out.println(strs.length);    }
       public static void main(String[] args) {     System.out.println(\"main\"); }
}
package com.cj.test;
import java.lang.reflect.Field;import java.util.Date;import org.junit.Test;
public class Demo3 {
       //public String name=\"李四\";      @Test      public void test1() throws Exception{        Class clazz = Person.class;        Person p = (Person)clazz.newInstance();        Field f = clazz.getField(\"name\");        String s = (String)f.get(p);        System.out.println(s);        //更改name的值        f.set(p, \"王六\");        System.out.println(p.name);      }
      @Test//private int age = 18;      public void test2() throws Exception{        Class clazz = Person.class;        Person p = (Person)clazz.newInstance();        Field f = clazz.getDeclaredField(\"age\");        f.setAccessible(true);        int age = (Integer)f.get(p);        System.out.println(age);        f.set(p, 28);        age = (Integer)f.get(p);        System.out.println(age);      }
      @Test//public static Date time;      public void test3() throws Exception{        Class clazz = Person.class;        Field f = clazz.getField(\"time\");        f.set(null, new Date());        System.out.println(Person.time);      }}

以上就是自己对Java中反射的一些总结。看完上边有关反射的东西, 对常用框架里的配置文件是不是有点思路了。

\"link-13\"

比如,上图是Spring配置文件里的常见的bean配置,现在看起来是不是可以用反射就可以简单的实现了:解析xml,然后把xml里的内容作为参数,利用反射创建对象。

拓展

1、除了上述的Spring配置文件里会用到反射生成bean对象,其他常见的MVC框架,比如Struts2、SpringMVC等等一些框架里还有很多地方都会用到反射。前端页面录入的一些信息通过表单或者其他形式传入后端,后端框架就可以利用反射生成对应的对象,并利用反射操作它的set、get方法把前端传来的信息封装到对象里。

感兴趣的话可以看下这篇:利用Java反射模拟一个Struts2框架,这篇里边包含了XML解析、反射的东西,模拟了一个Struts2的核心代码。等有时间了,我会发上来的。

2、框架的代码里经常需要利用反射来操作对象的set、get方法,来把程序的数据封装到Java对象中去。如果非常频繁的每次都使用反射来操作set、get方法进行设置值和取值的话,就太过于麻烦。所以JDK里提供了一套API,专门用于操作Java对象的属性(set/get方法),这就是内省。关于内省相关的内容我也写了一篇文章,会尽快发上来的。

3、平常用到的框架,除了配置文件的形式,现在很多都使用了注解的形式。其实注解也和反射息息相关:使用反射也能轻而易举的拿到类、字段、方法上的注解,然后编写注解解析器对这些注解进行解析,做一些相关的处理。

所以说不管是配置文件还是注解的形式,它们都和反射有关。注解和自定义注解的内容,我之前也写过:Java中的注解以及自定义注解。这一篇我也会尽快发上来的,感兴趣的小伙伴可以关注一下。

未经允许不得转载:搜云库 » Java反射机制详解:如何通过反射操作构造方法、方法和字段

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们