一、什么是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 个类:

  1. java.lang.Class

  2. 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 种:

  1. 根据 forName 静态方法:Class.forName(类的全限定名)。该方法是传入一个字符串参数,该参数是某个类的全限定完整包名。

  2. 根据类名直接获取:类名.class。调用某个类的 class 属性获取 Class 对象,比如 Student.class 返回 Student 类对应的 Class 对象。

  3. 根据对象获取:对象.getClass()。该方法是 java.lang.Object 类中的一个方法,所有 java 对象都可以调用该方法。

​ (三种获取 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() - 判断是否为某个类的实例

更多方法请查看 Class 的文档:https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html。

5.2 通过反射创建类实例方法

  1. 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();
  1. 通过 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 对象所表示的类的构造方法。

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html。

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,如下图

第二步:把上一小节的 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,如下图

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

详细的demo代码查看:github reflect 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 等。

等用途。

七、参考

java基础学习:java中的反射的更多相关文章

  1. Java基础学习-Java语言概述

    一.Java语言发展史 创始人:詹姆斯·高斯林(James Gosling) 公司:SUN——(Stanford University Network斯坦福大学网络公司) 1995年5月23日,Jav ...

  2. <java基础学习>JAVA 对象和类

    Java is an Object-Oriented Language. As a language that has the Object Oriented feature, Java suppor ...

  3. Java基础学习 -- Java(OOP)程序的设计原则

    避免代码复制.解决方案:函数.父类: 封装.尽量private每个类的成员变量,用操作封装数据,减少类与类之间成员变量的直接调用,而是调用method,降低耦合: 可扩展性最大化.尽量使用框架+数据的 ...

  4. Java基础学习中一些词语和语句的使用

    在Java基础学习中,我们刚接触Java会遇到一些词和语句的使用不清的情况,不能很清楚的理解它的运行效果会是怎么样的,如:break,continue在程序中运行效果及跳转位置, 1.先来看看brea ...

  5. Java基础学习-- 继承 的简单总结

    代码参考:Java基础学习小记--多态 为什么要引入继承? 还是做一个媒体库,里面可以放CD,可以放DVD.如果把CD和DVD做成两个没有联系的类的话,那么在管理这个媒体库的时候,要单独做一个添加CD ...

  6. Java基础学习笔记总结

    Java基础学习笔记一 Java介绍 Java基础学习笔记二 Java基础语法之变量.数据类型 Java基础学习笔记三 Java基础语法之流程控制语句.循环 Java基础学习笔记四 Java基础语法之 ...

  7. java基础学习总结——开篇

    java是我学习的第一门编程语言,当初学习java基础的时候下了不少功夫,趁着这段时间找工作之际,好好整理一下以前学习java基础时记录的笔记,当作是对java基础学习的一个总结吧,将每一个java的 ...

  8. Java基础学习笔记(一)

    Java基础学习笔记(一) Hello World 基础代码学习 代码编写基础结构 class :类,一个类即一个java代码,形成一个class文件,写于每个代码的前端(注意无大写字母) XxxYy ...

  9. java基础学习总结——java环境变量配置(转)

    只为成功找方法,不为失败找借口! 永不放弃,一切皆有可能!!! java基础学习总结——java环境变量配置 前言 学习java的第一步就要搭建java的学习环境,首先是要安装 JDK,JDK安装好之 ...

  10. JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API

    森林森 一份耕耘,一份收获 博客园 首页 新随笔 联系 管理 订阅 随笔- 397  文章- 0  评论- 78  JAVA基础学习day16--集合三-Map.HashMap,TreeMap与常用A ...

随机推荐

  1. RPA应用场景-报税机器人

    场景概述 报税机器人 所涉系统名称 税务网站 人工操作(时间/次) 53分钟 所涉人工数量 60 操作频率 每月 场景流程 1.通过RPA自动将财税信息从对应系统中导出 2.RPA根据不同的税务报表规 ...

  2. Linux文件的通配符

    通配符的作用:匹配文件名 常见的通配符: *:表示任意个字符(不包括隐藏文件) ?:单个任意字符(中文也算一个字符) []:表示匹配一范围或者其中一个 表示匹配范围: [a-z] --- 不但包括了小 ...

  3. Java:如何打印整个字符串数组?

    例: public static void main(String[] args) { String prodName = "雇员姓名,雇员唯一号"; String[] prodN ...

  4. ASPNET Core笔试题

    1.如何在ASP.NET Core中激活Session功能? 首先要添加session包. 其次要在configservice方法里面添加session.然后又在configure方法里面调用 use ...

  5. Linux,Centos系统下配置java Jdk(附下载地址)

    一.下载jdk 官网下载地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 需要登录Oracle ...

  6. 基于UniApp社区论坛多端开发实战

    什么是移动端WebApp 移动端WebApp: 泛指手持设备移动端的web 特点: - 类App 应用,运行环境是浏览器 - 可以包一层壳,成为App - 常见的混合应用: ionic, Cordov ...

  7. Linux关闭avahi-daemon服务

    avahi-daemon是一种Linux操作系统上运行在客户机上实施查找基于网络的Zeroconf service的服务守护进程. 该服务可以为Zeroconf网络实现DNS服务发现及DNS组播规范. ...

  8. 经典面试题:==和equals的区别

    1.== 既可以比较基本类型也可以比较引用类型.对于基本类型就是比较值,对于引用类型就是比较内存地址 2.equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过 ...

  9. llinux的mysql数据库完全卸载

    https://blog.csdn.net/qq_41829904/article/details/92966943https://www.cnblogs.com/javahr/p/9245443.h ...

  10. java 配置aop 写入无效

    一个项目不同的Module 含有相同的路径以及文件,配置的AOP的expression吸入日志无效,要点击包查看当前包是否是本Module下面的,不然调用无效. 改为本Module就行了