Java 在互联网上的飞速发展和使用,离不开诸如 Spring 、SpringMVC、Mybatis 等框架的推出,其极大简化了开发者的精力。
对于各式各样的框架,它们底层使用的依然是 Java 代码,只是它们通过利用 Java 提供的多种特性和技术,简化程序员所需要编写的代码,大大减少了开发时间。
而这篇,就是讲述 Java 框架的核心技术之一,Java 反射 。
1. 反射的出现 将类的各个组成部分封装成其他对象,这就是 Java 的反射机制,通过反射,我们可以得到每个类的各个组成部分。
由于反射的优秀特性,现代的主流 Web 中使用到的框架 Spring / MyBatis 等都大量使用了反射的技术,可以说,反射是框架的灵魂。
1.1 Java 代码经历的三个过程 那么反射是什么?作用在什么地方?要搞清楚这个问题,我们首先看一张图:
这张图简略的描述了一个 Java 程序从编写完成到执行经历的过程。
首先在 Source 源代码阶段 ,假设我们新建了一个 Person.java
文件 ,在里面有一个公有的 Person 类,类里面有成员变量,构造方法,成员方法。
编写好代码之后,我们要运行的话,首先需要 编译 ,使用 Java 自带的javac
命令编译后,此时在我们的目录下会生成一个 Java 字节码文件 Person.class
。
Person.class
文件中包含这个类文件的所有信息,主要有三部分内容。一部分是我们所有的成员变量,一部分是我们的构造方法,一部分是我们的成员方法。当然其中不止这些内容,还有类名等等其他信息,.class
文件是 Java 跨平台的基础。
编译好的 Java 字节码文件(.class
后缀)开始是在硬盘中的,而程序都是在内存中运行的,在使用之前我们应将其加载到内存中去。
如何将字节码加载到内存中呢?在 Java 中,负责加载硬盘上的字节码文件到内存中的工具称为 类加载器(ClassLoader) ,它是一个对象,它负责将类对应的字节码文件加载到内存中。
而在Java中,万物皆对象,在内存中,也有一个对象负责存储描述字节码文件中的信息,该对象为Class
类的对象,在 Java 的 api 中,我们也可以找到这个类:
在被加载的类 Class 对象中,包含了这个类所有共同的类型信息 。什么是共同的类型信息呢?就比如 每个类的成员变量,构造方法,成员方法,类名 ….等等。 这里我们也要注意一个点,一个 class 字节码文件对应一个 Class 对象,也就是说,无论你创建了多少个实例对象,在 JVM(Java 虚拟机) 或者说内存中只会存在一个 Class 对象,其负责描述类的共同类型信息。具体对应关系看下图:
于是在Class
对象中,每一个成员变量都被封装成一个Field
对象,而多个成员变量就形成了Field []
对象数组。构造方法,成员方法也是如此,在 Class 类中,他们的对应关系为:
组成部分
对象名
成员变量
Field
构造方法
Constructor
成员方法
Method
第二阶段加载类的步骤执行完后,JVM再根据Class
对象中的信息实例对象,至此,对象才被真正创建。
1.2 反射的思想 根据我们上面的分析,类在被使用前,应先被类加载器加载到内存中以Class
对象的方式存在,Class
对象将类的成员变量,构造函数,成员方法封装成不同的对象。
那么我们可不可以这样想,当一个Person类的对象在使用时,由于其共有的Class
对象中将该对象的成员变量,构造函数,成员方法等封装成了对象,我们是不是可以通过操作这些封装好的对象来操控Person对象中的相关信息?
答案时肯定的,这也就是反射的实现思想,在程序的运行过程中,能够知道程序的所有属性和方法,也能够随时调用改变其中的属性和方法,这就是反射机制
使用反射的优点如下:
可以在程序的运行过程中去操作这些对象
可以解耦,提高程序的阿可扩展性
知道了反射的概念之后,我们使用一些案例来分析反射的基本使用
2. 反射的基本使用 2.1 获取Class对象 通过上面的分析,我们知道了反射可以操控类的Class
对象,那么在使用反射之前,我们应该先获得类的Class
对象。
我们可以使用如下三种方式获得Class
对象,对应Java代码经历的三个阶段。
类还未被加载进内存,也就是源代码阶段使用
Class.forName(“全类名”) :将字节码文件加载进内存,返回Class对象。
类已经被加载进内存了,但是实例对象还没创建时使用
类名.class:通过类名的属性class获取
类已经被实例化,也就是已经有了实例对象时使用
对象名.getClass():利用Object类中定义的getClass()方法
文字描述可能不够透彻,我们使用代码来验证以下我们的结果。
我们打开idea ,新建一个java项目,名字为ReflectDemo,然后新建一个实体类Person,一个ReflectDemo1,此时项目结构如下:
然后我们在Person类中添加如下代码:
public class Person { private int age; private String name; public Person () { } public Person (int age, String name) { this .age = age; this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}' ; } }
相应的ReflectDemo1的代码如下:
public class ReflectDemo1 { public static void main (String[] args) throws ClassNotFoundException { Class cla1 = Class.forName("Person" ); System.out.println(cla1); Class cla2 = Person.class; System.out.println(cla2); Person p = new Person (); Class cla3 = p.getClass(); System.out.println(cla3); } }
运行结果:
可以看到,输出的结果是一样的,那么是不是他们都是同一个Class对象吗?
我也不确定,单纯靠输出不能完全判定,我们还需要通过一些验证代码来确定。
根据我们以前学过的知识,判断两个对象是否相等,也就是引用指向的内存空间是否相同,我们可以使用 ==
运算符。
那么我们添加代码如下:
System.out.println(cla1 == cla2); System.out.println(cla1 == cla3); System.out.println(cla2 == cla3);
然后运行:
结果都为true,他们的对象都是一样的。这说明了什么?
我们整理一下思路
首先cla1
对象我们通过Class.forName("Person")
获得,而此时类还为被加载。
cla2
对象我们通过Person.class
获得,此时类已经加载但是没有实例化。
cla3
对象我们通过p.getClass()
获得,此时类已经实例化。
通过对比,三个对象都是同一个对象,也就是说,对于同一个类,它在依次运行时只会被加载一次,且只存在一个Class
对象。
2.2 使用Class对象 关于Class对象的使用,主要分为四类:
成员变量Field对象的使用
构造方法Constructor对象的使用
成员方法Method对象的使用
对类名的获取
下面我们来分析他们的具体用法。
2.2.1 使用Field类操作成员变量 Class类中有关Field类的基本操作如下:
方法返回值
方法名
方法功能
Field
getField(String name)
获取指定name名称、具有public修饰符的变量(字段),包括继承字段。
Field[]
getFields()
获取所有具有public修饰符的变量(字段),包括继承字段
Field
getDeclaredField(String name )
获取指定name名称的变量(字段),且不考虑修饰符(包括private修饰的),不包括继承字段。
Field[]
getDeclaredFields()
获取类中所有变量(字段),不考虑修饰符(包括private修饰的),不包括继承字段
Field类中常用的方法:
方法返回值
方法名
方法功能
Object
get(Object obj)
返回指定对象上此 Field 对象所表示的字段的值
void
set(Object obj , Object value)
将指定对象(obj)变量上此 Field 对象表示的字段设置为指定的新值(value)
void
setAccessible(boolean flag)
将此对象的 accessible 标志设置为指定的布尔值,accessible 属性默认为false,设置为true后我们称为《暴力反射》
首先在Person
类修改代码如下:
public class Person { private int age; private String name; public int height = 180 ; protected String sex = "male" ; public int weight = 120 ; String hobby = "篮球" ; public Person () { } public Person (int age, String name) { this .age = age; this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "Person{" + "age=" + age + ", name='" + name + '\'' + ", height=" + height + ", sex='" + sex + '\'' + ", hobby='" + hobby + '\'' + '}' ; } }
然后新建一个ReflecField文件,添加如下代码:
import java.lang.reflect.Field;public class ReflectField { public static void main (String[] args) throws Exception { Class pes = Class.forName("Person" ); Person p = new Person (); System.out.println("**********输出结果**********" ); System.out.println("----------getField(String name )的使用----------" ); try { Field age = pes.getField("age" ); System.out.println(age); }catch (NoSuchFieldException nfe){ nfe.printStackTrace(); } Field height = pes.getField("height" ); System.out.println(height); System.out.println("----------getFields()的使用----------" ); Field[] fields = pes.getFields(); for (Field f: fields){ System.out.println(f); } System.out.println("----------getDeclaredField(String name )的使用----------" ); Field age1 = pes.getDeclaredField("age" ); Field name1 = pes.getDeclaredField("name" ); Field sex = pes.getDeclaredField("sex" ); System.out.println(sex); System.out.println(age1); System.out.println("----------getDeclaredFields()的使用----------" ); Field[] fields1 = pes.getDeclaredFields(); for (Field f : fields1){ System.out.println(f); } System.out.println("----------获取变量的值----------" ); System.out.println(height.get(p)); System.out.println(sex.get(p)); try { System.out.println(age1.get(p)); }catch (IllegalAccessException iae){ iae.printStackTrace(); } age1.setAccessible(true ); System.out.println(age1.get(p)); System.out.println("----------设置变量的值----------" ); System.out.println("修改所有属性值:" + p.toString()); height.set(p,170 ); try { name1.set(p,"小千" ); }catch (IllegalAccessException iae){ iae.printStackTrace(); } System.out.println(p.toString()); name1.setAccessible(true ); name1.set(p,"小千" ); System.out.println(p.toString()); } }
运行结果为:
**********输出结果********** ----------getField(String name )的使用---------- java.lang.NoSuchFieldException: age 4at java.lang.Class.getField(Class.java:1703 ) 4at ReflectField.main(ReflectField.java:23 ) public int Person.height----------getFields()的使用---------- public int Person.heightpublic int Person.weight----------getDeclaredField(String name )的使用---------- protected java.lang.String Person.sexprivate int Person.age----------getDeclaredFields()的使用---------- private int Person.ageprivate java.lang.String Person.namepublic int Person.heightpublic int Person.weightprotected java.lang.String Person.sexjava.lang.String Person.hobby ----------获取变量的值---------- heght:180 sex:male java.lang.IllegalAccessException: Class ReflectField can not access a member of class Person with modifiers "private" 4at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102 ) 4at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296 ) 4at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288 ) 4at java.lang.reflect.Field.get(Field.java:390 ) 4at ReflectField.main(ReflectField.java:67 ) age:0 ----------设置变量的值---------- 修改所有属性值:Person{age=0 , name='null' , height=180 , sex='male' , hobby='篮球' } java.lang.IllegalAccessException: Class ReflectField can not access a member of class Person with modifiers "private" 4at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102 ) 4at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296 ) 4at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288 ) 4at java.lang.reflect.Field.set(Field.java:761 ) 4at ReflectField.main(ReflectField.java:84 ) Person{age=0 , name='小千' , height=170 , sex='male' , hobby='篮球' }
2.2.2 使用Constructor类 Class类中有关Constructor类的基本方法如下:
方法返回值
方法名
方法功能
Constructor<T>
getConstructor(Class<?>....parameterTypes)
返回指定参数类型、具有public访问权限的构造函数对象
Constructor<?> []
getConstructors()
返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor<T>
getDeclaredConstructor(Class<?>...parameterTypes)
返回指定参数类型、且不考虑权限修饰符(包括private)声明的构造函数对象
Constructor<?>[]
getDeclaredConstructors()
返回所有声明的构造函数对象、且不考虑权限修饰符(包括private)
T
newInstance()
调用类的空参构造创建此 Class 对象所表示类的一个实例,返回创建的实例,
在Person类中添加如下代码:
private Person (String name) { this .name = name; }
新建一个ReflectConstructor,添加如下代码:
import java.lang.reflect.Constructor;public class ReflectConstructor { public static void main (String[] args) throws Exception { Class pes = Class.forName("Person" ); System.out.println("**********输出结果**********" ); System.out.println("------------getConstructor(Class<?>....parameterTypes)的使用-----------" ); Constructor constructor = pes.getConstructor(int .class,String.class); Object p1 = constructor.newInstance(10 , "小千" ); System.out.println(p1); System.out.println(); System.out.println("------------getConstructors()的使用-----------" ); Constructor[] constructors = pes.getConstructors(); for (Constructor c: constructors){ System.out.println(c); } System.out.println(); System.out.println("------------getDeclaredConstructor(Class<?>...parameterTypes)的使用-----------" ); Constructor constructor1 = pes.getDeclaredConstructor(String.class); constructor1.setAccessible(true ); Object p2 = constructor1.newInstance("大千" ); System.out.println(p1); System.out.println(); System.out.println("------------getDeclaredConstructors()的使用-----------" ); Constructor[] constructors1 = pes.getDeclaredConstructors(); for (Constructor c: constructors1){ System.out.println(c); } System.out.println(); System.out.println("------------newInstance()的使用-----------" ); Object p3 = pes.newInstance(); System.out.println(p3); } }
运行结果如下:
**********输出结果********** ------------getConstructor(Class<?>....parameterTypes)的使用----------- Person{age=10, name='小千', height=180, sex='male', hobby='篮球'} ------------getConstructors()的使用----------- public Person(int,java.lang.String) public Person() ------------getDeclaredConstructor(Class<?>...parameterTypes)的使用----------- Person{age=10, name='小千', height=180, sex='male', hobby='篮球'} ------------getDeclaredConstructors()的使用----------- public Person(int,java.lang.String) public Person() private Person(java.lang.String) ------------newInstance()的使用----------- Person{age=0, name='null', height=180, sex='male', hobby='篮球'}
2.2.3 使用Method类 Class 类中有关Method类的基本操作如下:
方法返回值
方法名
方法功能
Method
getMethod(String name, Class<?>....parameterTypes)
返回一个指定参数类型的已声明的 Method 对象,包括继承的父类 public 方法。
Method []
getMethods()
返回该Class对象中所有由 public 权限修饰符且已声明的 Method 方法对象,包括继承的父类 public 方法。
Method
getDeclaredMethod(String name, Class<?>...parameterTypes)
返回指定参数类型、且不考虑权限修饰符(包括private)的已声明的声明的方法 Method 对象,但不包括继承的方法。
Method[]
getDeclaredMethods()
返回所有已声明的方法 Method 对象、且不考虑权限修饰符(包括private),但不包括继承的方法
在Person类中添加如下代码:
public void show (String s) { System.out.println(s); System.out.println("运行 public show" ); } private void display () { System.out.println("运行 private display" ); }
新建一个 ReflectMethod 类,添加代码如下:
import java.lang.reflect.Method;public class ReflectMethod { public static void main (String[] args) throws Exception { Class pes = Class.forName("Person" ); Person p = new Person (); System.out.println("**********输出结果**********" ); System.out.println("----------getMethod(String name, Class<?>....parameterTypes)的使用----------" ); Method show = pes.getMethod("show" , String.class); show.invoke(p,"千" ); System.out.println("----------getMethods()的使用----------" ); Method[] methods = pes.getMethods(); for (Method m : methods) { System.out.println(m); } System.out.println("----------getDeclaredMethod(String name, Class<?>...parameterTypes)的使用----------" ); Method display = pes.getDeclaredMethod("display" ); System.out.println(display); display.setAccessible(true ); display.invoke(p); System.out.println("----------getDeclaredMethods()的使用----------" ); Method[] declaredMethods = pes.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(m); } } }
输出结果:
**********输出结果********** ----------getMethod(String name, Class<?>....parameterTypes)的使用---------- 千 运行 public show ----------getMethods()的使用---------- 能够获得Object类的方法对象 public java.lang.String Person.toString()public java.lang.String Person.getName()public void Person.setName(java.lang.String)public void Person.show(java.lang.String)public void Person.setAge(int )public int Person.getAge()public final void java.lang.Object.wait() throws java.lang.InterruptedExceptionpublic final void java.lang.Object.wait(long ,int ) throws java.lang.InterruptedExceptionpublic final native void java.lang.Object.wait(long ) throws java.lang.InterruptedExceptionpublic boolean java.lang.Object.equals(java.lang.Object)public native int java.lang.Object.hashCode()public final native java.lang.Class java.lang.Object.getClass()public final native void java.lang.Object.notify()public final native void java.lang.Object.notifyAll()----------getDeclaredMethod(String name, Class<?>...parameterTypes)的使用---------- private void Person.display()运行 private display ----------getDeclaredMethods()的使用---------- 不能获得Object类的方法对象 public java.lang.String Person.toString()public java.lang.String Person.getName()public void Person.setName(java.lang.String)private void Person.display()public void Person.show(java.lang.String)public void Person.setAge(int )public int Person.getAge()
2.2.4 获取类名 获取类名的方法相对简单,通过以下代码:
Class pes = Class.forName("Person" ); String className = pes.getName(); System.out.println(className);
运行结果:
OK,反射使用基本结束。
3. 参考链接: 深入理解Java类型信息(Class对象)与反射机制