学堂 学堂 学堂公众号手机端

java基础学习:java中的反射

lewis 1年前 (2024-04-26) 阅读数 14 #技术
一、什么是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.Classjava.lang.reflectjava.lang.reflect.Constructor, 获取构造方法,Class 对象所表示类的构造方法Java.lang.reflect.Field, 字段成员,Class 对象所表示的类的成员变量,通过它可以动态修改成员变量值,包含 privateJava.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

此demo完整详细代码在 github 上:​​reflect demo​​

其他一些常用写法:
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 个,分别为获取单个构造方法和获取多个构造方法。

方法如下:

方法

使用说明

public Constructor<?>[]​​getConstructors()​​

返回所有public权限的构造函数的对象数组。

public Constructor<?>[]​​getDeclaredConstructors()​​

获取所有构造函数方法,包括所有权限public,private,protected,default权限。

public Constructor​​getConstructor(Class<?>... parameterTypes)​​

获取单个public权限的构造方法。

public Constructor​​getDeclaredConstructor(Class<?>... parameterTypes)​​

获取单个构造方法,包括所有权限public,private,protected,default权限。

调用构造方法:

newInstance(Object... initargs) ,使用此 Constructor 对象表示的构造方法,用它来创建类的新实例,并用指定的初始化参数初始化该实例。上面也有讲到过该方法使用。

写一个 demo 例子:

此demo完整详细代码在 github 上:​​github-reflect construct​​,下面说说编写代码步骤。

第一步:新建一个 reflectconstructor 包,然后新建 2 个 java 文件,reflectconstructdemo1.java 和 student.java,如下图

[图片上传失败...(image-61e904-1662428287681)]

第二步:把上一小节的 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());

此demo完整详细代码在 github 上:​​github-reflect constructor​​

5.4 获取成员字段 Field

通过 Class 类提供的方法来获取成员字段信息,主要方法有 4 种,也分为获取单个和多个成员,是公有还是私有,还是所有权限都能获取。

方法如下:

方法

使用说明

public Field​​getField(String name)​​

根据名字获取单个public权限的字段

public Field​​getDeclaredField​​(String name)

根据名字获取某个字段,字段权限可以是所有,包括private

public Field[]​​getFields​​()

获取所有public权限字段

public Field[]​​getDeclaredFields​​()

获取所有字段,字段权限可以是所有,包括private

写一个 demo 例子

第一步:创建一个 reflectfield 的包,然后新建 2 个 java 文件,reflectfielddemo1.java 和 student.java,如下图

[图片上传失败...(image-aacc78-1662428287681)]

第二步:把上一小节 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
关于​​Field​​的一些其他常用操作方法:

方法名

使用说明

Object get(Object obj)

返回指定对象上字段值

void set(Object obj, Object value)

指定对象上为field字段设置新值

Class<?> getType()

field字段表示的声明的字段类型

Class<?> getDeclaringClass()

field字段所在类的Class对象

String getName()

返回字段名称

void setAccessible(boolean flag)

对象的 accessible 标志设置,可以设置字段的访问性。比如设置为true表示其可写

更多方法可以查看​​Field​​类的 API:​​https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html​​

此demo完整详细代码在 github 上:​​github-reflect field​​

5.5 获取成员方法 Method

通过 Class 类获取 Method 对象的方法,与上面获取字段field方法相似,也有 4 种,分为获取单个和获取多个方法。

方法

使用说明

public Method[]​​getMethods​​()

获取所有public的方法,包含父类的方法

public Method​​getDeclaredMethod​​(String name, Class<?> ...parameterTypes)

获取所有的方法,包括private

public Method​​getMethod​​(String name, Class<?>... parameterTypes)

获取单个public的方法

public Method​​getDeclaredMethod​​(String name, Class<?>... parameterTypes)

根据名字获取所有的方法

demo 例子

第一步:与上一小节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);
方法调用 invoke
// 根据参数获取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)​​。

说明:上面所有的代码以 github 上的为准:​​reflect demo系列​​

六、反射有哪些用途动态代理:动态代理可以通过反射来实现。注解:注解也是用反射来实现。注解利用反射机制来调用注解的解释器。开发框架:比如 Spring 框架。Spring 中的 XML 配置 Bean 等
版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门