一、什么是 java 反射
什么是 java 的反射?
说到反射,写这篇文章时,我突然想到了人的”反省“,反省是什么?吾一日三省吾身,一般就是反思自身,今天做了哪些对或错的事情。
java 的反射,我觉得有同样的思想。当然 java 反射要“反思”的是 java 程序在运行时类自己的信息,它获取的信息就是它自身类的详细信息。
类的哪些详细信息呢?比如类或对象的成员变量、方法等。然后可以对这些信息加以修改,从而调整 java 的运行逻辑。
java 反射 API 提供了非常丰富的工具集,反射 API 能够获取对象的变量,方法等成员,从而可以动态的操纵 java 代码程序。文章后面会介绍这些反射 API(反射相关的类)的一些用法。
为什么反射能得到 java 程序运行时类的信息呢?这就要从 java 的虚拟机 jvm 说起。
二、虚拟机 jvm 加载文件
Java 虚拟机(Java Virtual Machine):用于执行编译后的 java 程序的虚拟容器。jvm 可以跨操作系统使用。
jvm 内部结构分为 3 部分:类加载器 classload 子系统、运行时数据区、执行引擎。
以 .java
结尾的文件是不能直接在 jvm 上运行,它必须通过 javac 编译为以 .class
为后缀结尾的字节码文件才能运行。
java 文件被编译为 .class 的文件后,java 文件中各种对象的信息就确定下来了,存在于 .class 文件里。通过 java 的反射就可以获取里面的信息。
三、反射原理简析
在上一小节简单了解了文件加载内容,就是 java 文件经过编译后变成 .class 文件,类的各种信息就存储在 .class 文件中了,所以反射才能获取到类的各种信息。
java 代码编译为字节码的 .class 类文件,那 .class 文件里都有什么格式是什么?
class 文件结构采用类似 c 语言的结构体来存储数据。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
复制代码
它由 2 部分组成:无符号的数和表。所有的表都习惯以 _info
结尾。
无符号的数属于基本的数据类型,一 u1,u2,u4,u8 分别来表示 1 个字节,2 个字节,4 个字节和 8 个字节的无符号数,无符号数可以用来表示数字、索引引用、数量值。
表用于描述有层次关系的复合结构的数据,整个 class 文件本质就是一张表。
类的信息都存储在 .class 文件里,当把 .class 文件读入内存时,就会为之创建一个 Class 对象。
这里又涉及到虚拟机加载类的机制。周志明的 《深入理解java虚拟机》 里有一节讲类加载的内容,上面 class 文件结构也来自本书,可以好好看一看此书。
简单说:java 虚拟机把描述类的数据 class 文件加载到内存,并对数据进行效验、转换解析和初始化,最终形成可以被虚拟机直接使用的类型,这就是虚拟机的类加载机制。
在 java 语言里,类的加载和连接过程都是在程序运行期间完成的,虽然有性能开销,但是为 java 应用程序提供了高度的灵活性。比如反射就是发生在 java 运行时完成的。
在类加载阶段,虚拟机会在 java 堆中生成一个代表这个类的 java.lang.Class 对象,通过这个 Class 对象就可以访问到 jvm 中的这个类。
Class 类与 class 是不同,Class 是实实在在存在于 java.lang.Class 包中的一个类。
反射:获取 Class 类对象及其类内部成员信息(属性, 方法, 构造函数等)以及控制实例对象的能力
四、反射功能常用的类
在 java 中,要使用反射功能,主要用到下面 2 个类:
java.lang.Class
java.lang.reflect
java.lang.reflect.Constructor, 获取构造方法,Class 对象所表示类的构造方法
Java.lang.reflect.Field, 字段成员,Class 对象所表示的类的成员变量,通过它可以动态修改成员变量值,包含 private
Java.lang.reflect.Method,方法成员,Class 对象所表示的类的方法成员,通过它可以动态调用对象的方法
Java.lang.reflect.Modifier,对类和成员访问修饰符进行解码
五、反射功能的使用
5.1 获取 Class 类对象的方法
获取 class 类对象的方法,主要有 3 种:
Class.forName(类的全限定名)
类名.class
对象.getClass()
复制代码
(三种获取 Class 对象的方法)
先举一个小例子来看看这 3 种获取 Class 对象的用法。
java v1.8
第一步:用 IDEA 新建 maven 项目,名字叫 JavaBasicDemos
目录如下:
JavabasicDemos
|-.idea
|-src
|-main
| |-java
| | |-org.example
| | |-Main.java
| |-resource
|-test
复制代码
把上面的 org.example 改成 org.basicdemo。然后在 org.basicdemo 下新建 reflect/reflectdemo1.java,student.java 文件,目录如下:
第二步:编写 student.java 代码
package org.basicdemo.reflect;
public class student {
private String name = "Tom";
private int age;
public student(){}
public student(String name, int age) {
this.name = name;
this.age = age;
}
private void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
复制代码
第三步:编写 reflectdemo1.java
package org.basicdemo.reflect;
public class reflectdemo1 {
public static void main(String[] args) {
try {
Class<?> stuclz1 = Class.forName("org.basicdemo.reflect.student");
System.out.println("Class.forName: " + stuclz1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
student stu = new student();
Class stuclz2 = stu.getClass();
System.out.println("对象.getClass(): " + stuclz2);
Class stuclz3 = student.class;
System.out.println("类名.class: " + stuclz3);
}
}
复制代码
点击 IDEA 上运行程序的绿色三角形按钮,输出如下:
Class.forName: class org.basicdemo.reflect.student
对象.getClass(): class org.basicdemo.reflect.student
类名.class: class org.basicdemo.reflect.student
复制代码
其他一些常用写法:
Class<?> clazz = Class.forName("Student");
System.out.println(clazz);
// or
Class cls = Class.forName("Student");
System.out.println(cls);
// 以前最常用反射获取jdbc
Class.forName("com.mysql.jdbc.Driver.class").
复制代码
Class 类的其它一些方法:
getName() - 获取完整的类名,包括包名
getSimpleName() - 获取类名,不包括包名
isInterface() - 判断 Class 对象是否表示一个接口
getInterfaces() - 表示 Class 对象所引用的类所实现的所有接口
isInstance() - 判断是否为某个类的实例
5.2 通过反射创建类实例方法
newInstance() 方法,Class 对象提供的 newInstance() 方法,来创建 Class 对象对应类的实例
复制代码
Class<Student> clz = Studnt.class
Student stu = clz.newInstance()
// 或者
Student stu = Student.class.newInstance()
//==========
Class<?> c = String.class;
Object str = c.newInstance();
复制代码
通过 Class 对象的构造器
通过 Class 对象获取 Constructor,再调用 Constructor 对象的 newInstance() 方法来创建对象
// 获取构造方法 Integer(int)
Construct construct1 = Integer.class.getConstructor(int.class)
Integer n1 = construct1.newInstance(123) // 调用构造方法
System.out.println(n1);
// 获取构造方法 Integer(String)
Constructor construct2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) construct2.newInstance("567");
System.out.println(n2);
复制代码
两种方法区别:
newInstance() 的局限是,它只能调用该类的 public 无参数构造方法。如果构造方法带有参数,或者不是 public,就无法直接通过 Class.newInstance() 来调用。
为了调用任意的构造方法,反射 API 提供了 Constructor 对象,它包含一个构造方法的所有信息,可以创建一个实例。
5.3 反射获取构造方法
通过反射来获取构造方法,然后使用。
java 反射里的 Constructor 类,Class Constructor ,这个 Constructor 类表示的是 Class 对象所表示的类的构造方法。
java.lang.Object
java.lang.reflect.AccessibleObject
java.lang.reflect.Executable
java.lang.reflect.Constructor<T>
复制代码
通过 Class 类来获取类的构造方法主要有 4 个,分别为获取单个构造方法和获取多个构造方法。
newInstance(Object... initargs) ,使用此 Constructor 对象表示的构造方法,用它来创建类的新实例,并用指定的初始化参数初始化该实例。上面也有讲到过该方法使用。
写一个 demo 例子:
此 demo 完整详细代码在 github 上: github-reflect construct ,下面说说编写代码步骤。
第一步:新建一个 reflectconstructor 包,然后新建 2 个 java 文件,reflectconstructdemo1.java 和 student.java,如下图
第二步:把上一小节的 student.java 代码复制过来,然后增加几个构造函数
student(String name) {
System.out.println("(student)private construct-age: "+age);
}
public student(){
System.out.println("no args");
}
public student(String name, int age) {
this.name = name;
this.age = age;
}
private student(int age) {
System.out.println("private construct-age: "+age);
}
复制代码
第三步:在 reflectconstructdemo1.java 里编写获取构造函数方法
首先获取 Class 对象:
Class stuclz = Class.forName("org.basicdemo.reflectconstructor.student");
复制代码
获取所有公有构造函数方法 getConstructors()
// 获取所有公有(public)构造方法
System.out.println("===========获取所有公有构造方法=========");
Constructor[] consarr = stuclz.getConstructors();
for(Constructor c : consarr) {
System.out.println(c);
}
复制代码
获取所有的构造函数方法 getDeclaredConstructors()
// 获取所有(public,protected,private,default)的构造方法
System.out.println("===========获取所有的构造方法=========");
Constructor[] consall = stuclz.getDeclaredConstructors();
for(Constructor c : consall) {
System.out.println(c);
}
复制代码
获取单个构造函数方法(公有、无参的方法)
// 获取单个构造方法,公有无参的构造方法
System.out.println("===========获取单个公有、无参数的构造方法=========");
try {
Constructor con = stuclz.getConstructor(null);
System.out.println("con: " + con);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
复制代码
获取单个私有 private 构造方法
System.out.println("===========获取单个私有private构造方法=========");
Constructor con = stuclz.getDeclaredConstructor(int.class);
System.out.println(con);
// 调用设置能访问
con.setAccessible(true); // 因为是私有,所以必须设置能访问
// 创建 student 对象
student stu = (student) con.newInstance(12);
stu.setAge(13);
System.out.println("age: "+stu.getAge());
复制代码
5.4 获取成员字段 Field
通过 Class 类提供的方法来获取成员字段信息,主要方法有 4 种,也分为获取单个和多个成员,是公有还是私有,还是所有权限都能获取。
第一步:创建一个 reflectfield 的包,然后新建 2 个 java 文件,reflectfielddemo1.java 和 student.java,如下图
第二步:把上一小节 reflectconstructor 包里的 student 类代码复制到这里的 student.java 里,然后添加几个字段
public String address;
public int grade;
protected String email;
String phone;
复制代码
第三步:编写获取字段的方法
获取 Class 对象
// 获取 Class 对象
Class stuClz = Class.forName("org.basicdemo.reflectfield.student");
复制代码
获取所有字段的方法
// 获取所有 public 权限的字段
System.out.println("==========获取所有 public 权限的字段===========");
Field[] fieldArr = stuClz.getFields();
for(Field f : fieldArr) {
System.out.println(f+" - ("+f.getDeclaringClass() +") - ("+f.getName()+":"+f.getType()+")");
}
// 获取所有权限的字段,包括private
System.out.println("==========获取所有权限的字段,包括private===========");
Field[] fieldsArr = stuClz.getDeclaredFields();
for(Field f : fieldsArr) {
System.out.println(f);
}
复制代码
获取单个字段方法
// 根据名字获取单个public字段
System.out.println("===========根据名字获取public字段============");
Field addressField = stuClz.getField("address");
System.out.println(addressField);
// 根据反射来设置下这个字段
Object obj = stuClz.getConstructor().newInstance();
// 用 set 方法来设置字段的值
addressField.set(obj, "setTestValue");
// 打印设置的值
student stu = (student) obj;
System.out.println("print address value: " + stu.address);
// 根据名字获取某个字段,字段权限包括所有,也包括private
System.out.println("=========根据名字获取某个字段,字段权限包括所有,也包括private=======");
// 来获取一个 private 字段
Field nameField = stuClz.getDeclaredField("name");
System.out.println(nameField);
// 没有设置前的name值
System.out.println("name value before setting: "+stu.getName());
// 来设置值
nameField.setAccessible(true); // 因为是private,所以先要设置可访问。相当于打开一个开关,原本是不可以写的。
nameField.set(obj, "jimmy");
System.out.println("name value after setting: " + stu.getName());
复制代码
IDEA 上代码运行输出:
==========获取所有 public 权限的字段===========
public java.lang.String org.basicdemo.reflectfield.student.address - (class org.basicdemo.reflectfield.student) - (address:class java.lang.String)
public int org.basicdemo.reflectfield.student.grade - (class org.basicdemo.reflectfield.student) - (grade:int)
==========获取所有权限的字段,包括private===========
private java.lang.String org.basicdemo.reflectfield.student.name
private int org.basicdemo.reflectfield.student.age
public java.lang.String org.basicdemo.reflectfield.student.address
public int org.basicdemo.reflectfield.student.grade
protected java.lang.String org.basicdemo.reflectfield.student.email
java.lang.String org.basicdemo.reflectfield.student.phone
===========根据名字获取public字段============
public java.lang.String org.basicdemo.reflectfield.student.address
no args
print address value: setTestValue
=========根据名字获取某个字段,字段权限包括所有,也包括private=======
private java.lang.String org.basicdemo.reflectfield.student.name
name value before setting: Tom
print name value after setting: jimmy
复制代码
第一步:与上一小节 filed 一样,先新建一个 reflectmethod 包,然后在里面新建 2 个 java 文件,reflectmethoddemo1.java 和 student.java,如下图:
第二步:把上一小节的 student.java 代码复制到这里
并增加一个基础 student.java 的 class TomStudent,
class TomStudent extends student {
private void printName() {
System.out.println("a student name: Tom");
}
public int getTomAge() {
return 12;
}
public void setTomeAge(int age) {
this.setAge(age);
}
}
复制代码
第三步:编写获取方法的代码
获取多个的方法
// 获取所有public method方法
System.out.println("=============获取所有public method方法,包括继承父类的===============");
Method[] methodArr = stuClz.getMethods();
for(Method m:methodArr) {
System.out.println(m); // 不仅打印出了 TomStudent 所有 public 方法,它继承的方法也打印出来
}
复制代码
获取单个的方法:
// 根据参数获取public的方法,包含继承自父类的方法
System.out.println("=======根据参数获取public的方法,包含继承自父类的方法======");
Method method = stuClz.getMethod("setAge", int.class);
System.out.println(method);
复制代码
// 根据参数获取public的方法,包含继承自父类的方法
System.out.println("=======根据参数获取public的方法,包含继承自父类的方法======");
Method method = stuClz.getMethod("setAge", int.class);
System.out.println(method);
// 反射调用方法
Object obj = stuClz.newInstance();
method.invoke(obj, 12);
student stu = (student)obj;
System.out.println(stu.getAge());
复制代码
如果是调用 private 方法,一定要设置 setAccessible(true)
。
六、反射有哪些用途
等用途。
评论