Java Reflection(从浅入深理解反射)

本节的代码链接:reflection

1. 反射的由来

反射机制允许程序执行期借助于Reflection API取得任何类的内部信息,如成员变量、构造器、成员方法等,并能操作对象的属性及方法,在设计模式和框架底层都会用到。

1.1 引入需求

编写框架的时候有一个常见的需求:通过外部配置文件,在不修改源码的情况下,来扩展功能。

有这么一个类:

public class Obj {
    private String name = "mimi";

    public Obj() {
    }

    public Obj(String name) {
        this.name = name;
    }

    public void sayHi() {
        System.out.println(this.name + " say: hi!");
    }
}

试着思考:在了解反射之前,你是否可以使用现有技术,通过一个指定了某个类的全限定类名和方法.properties配置文件,实例出对象并且调用其中的方法呢?

1.2 尝试通过I/O解决

我们知道,通过全限定类名确实可以实例化一个对象

Obj obj = new com.chenshu.Obj();

那我就先通过I/O把Obj类的全限定类名和方法拿出来呗,使用Properties类,可以轻松的读写文件:

public class Main {
    public static void main(String[] args) throws IOException {
        //1.读写文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/static/re.properties"));
        String classPathName = properties.getProperty("classPath");
        String methodName = properties.getProperty("method");
        System.out.println("全限定类名:" + classPathName);
        System.out.println("方法名:" + methodName);
    }
}

拿到全限定类名和方法名后,我们开始犯难了,拿到了Obj类的全限定类名后,也不可能通过这个 String 创建对象啊,这时就要用到反射机制了。

2. 反射机制快速入门

2.1 加载类

通过一个类名为Class的类来获取到Obj类,可以把下面的clazz对象看成Obj类的图纸:

Class clazz = Class.forName(classPathName);

2.2 获得实例

通过clazz得到 com.chenshu.Obj 的对象实例:

Object o = clazz.newInstance();

2.3 获得方法

java.lang.reflect 包中有一个 Method 类可以从 clazz 中接收方法,也就是把方法视为一个对象,并通过方法.invoke(对象)来实现调用:

Method method = clazz.getMethod(methodName);
method.invoke(o);

3. 反射原理

加载完类之后,在中就产生了一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一个镜子,透过这个镜子看到类的结构,所以,形象的称之为反射。

为什么能够通过这样一个Class对象来进行创建实例,操作属性,操作方法呢?

以前面提到的Obj类为例子:

Obj.java这样一个java代码,会在编译时期被转换为Obj.class的字节码文件

Untitled Diagram.drawio.png

当Java程序在“运行阶段”首次使用某个类时,这个类的字节码会被加载到JVM中。这个阶段称为“加载阶段”。加载不仅仅是将类的字节码读入内存,还会创建一个代表该类的Class对象。这个Class对象可以看作是一个可以操作的数据结构。

直到类加载完了才生成了这么一个对象

Untitled Diagram.drawio-6.png

由于Obj知道自身是属于哪一个对象,因此能够getClass()方法找到它的Class对象,并且能通过Class对象提供的一系列API来修改这个数据结构,进而进行这些操作:创建对象、调用方法、操作属性等

4. 反射的主要类

//加载类
Class clazz = Class.forName(classPathName);
Object o = clazz.newInstance();

4.1 java.lang.reflect.Field

代表类的成员变量,字段 (Field) 对象表示某个类的成员变量

Field nameField = clazz.getField("age");

通过get获取属性值,也可以通过set修改属性值:

nameField.set(o, 30);
System.out.println("修改age后的值为:" + nameField.get(o));

运行结果:

修改age后的值为:30

4.2 java.lang.reflect.Method

代表类的方法,方法(Method)对象表示某个类的方法

Method method = clazz.getMethod(methodName);
//通过invoke调用方法
method.invoke(o);

前面已经使用过,这里就不多说了。

4.3 java.lang.reflect.Constructor

代表类的构造方法,构造函数(Constructor)对象表示构造器

Constructor constructor1 = clazz.getConstructor();
Constructor constructor2 = clazz.getConstructor(String.class);

在程序中可以通过构造器来构造对象:

//使用无参构造器
Object object = constructor1.newInstance();
//使用带参构造器
Object object1 = constructor2.newInstance("test");
System.out.println(object);
System.out.println(object1);

运行结果:

Obj{name='mimi', age=20}
Obj{name='test', age=20}

5. java.lang.Class

5.1 概述

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是当一个类第一次被使用时,ClassLoader(类加载器)调用loadClass()方法创建出来的
  3. 对于某个类的字节码文件,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个 Class 实例所生成,因此可以通过getClass()获得
  5. 通过Class可以完整地得到一个类的完整结构,并通过一系列API来操作这个类
  6. Class对象是存放在堆的类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)

5.2 获取Class对象的六种方式

  1. Class.forName() 应用场景:通过配置文件获取
String classPathName = "com.chenshu.Obj";
Class cls1 = Class.forName(classPathName);
  1. 类名.class 应用场景:用于参数传递
Class cls2 = Obj.class;
  1. 对象.getClass() 应用场景:有对象实例
Obj obj = new Obj();
Class cls3 = obj.getClass();
  1. classLoader.loudClass()
ClassLoader classLoader = obj.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(classPathName);
  1. 基本数据类型.class
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
  1. 包装类型.TYPE
Class<Integer> type = Integer.TYPE;
Class<Character> type1 = Character.TYPE;

5.3 常用API及使用

定义一个下面的类用于测试:

class User {
    //四种修饰符修饰的属性
    public String name;
    protected int age;
    String gender;
    private double salary;
    
    //无参公开构造器
    public User() {
        
    }
    //有参公开构造器
    public User(String name) {
        this.name = name;
    }
    //有参私有构造器
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //四种修饰符的方法
    public void m1() {

    }
    protected void m2() {

    }
    void m3() {

    }
    private void m4() {

    }
}

先获取User的Class对象,后面的API都通过这个Class对象来使用:

Class<?> userCls = Class.forName("com.chenshu.test.User");

  1. getName()获取全类名
System.out.println(userCls.getName());

运行结果:

com.chenshu.test.User

  1. getSimpleName()获取简单类名
System.out.println(userCls.getSimpleName());

运行结果:

User

  1. getFields()获取本类和父类的public属性
Field[] fields = userCls.getFields();
for (Field field : fields) {
    System.out.println("本类及父类的public属性:" + field.getName());
}

运行结果:

本类及父类的public属性:name

  1. getDeclaredFields()获取所有本类的属性
Field[] declareFields = userCls.getDeclaredFields();
for (Field field : declareFields) {
    System.out.println("本类的所有属性:" + field.getName());
}

运行结果:

本类的所有属性:name
本类的所有属性:age
本类的所有属性:gender
本类的所有属性:salary

  1. getMethods()获取本类及父类的public方法
Method[] methods = userCls.getMethods();
for (Method method : methods) {
    System.out.println("本类及父类的public方法:" + method.getName());
}

运行结果:

本类及父类的public方法:m1
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:equals
本类及父类的public方法:toString
本类及父类的public方法:hashCode
本类及父类的public方法:getClass
本类及父类的public方法:notify
本类及父类的public方法:notifyAll

  1. getDeclaredMethods() 获取本类的所有方法
Method[] declareMethods = userCls.getDeclaredMethods();
for (Method method : declareMethods) {
    System.out.println("本类的所有方法:" + method.getName());
}

运行结果:

本类的所有方法:m1
本类的所有方法:m2
本类的所有方法:m3
本类的所有方法:m4

  1. getConstructors() 获取本类的public构造方法(没有父类)
Constructor<?>[] constructors = userCls.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println("本类的public构造方法:" + constructor.getName());
}

运行结果:

本类的public构造方法:com.chenshu.test.User
本类的public构造方法:com.chenshu.test.User

  1. getDeclaredConstructors() 获取本类所有构造方法
Constructor<?>[] declaredConstructors = userCls.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
    System.out.println("本类的所有构造方法:" + constructor.getName());
}

运行结果:

本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User

  1. getPackage() 获取该类所属包
System.out.println(userCls.getPackage());

运行结果:

package com.chenshu.test

  1. getSuperclass() 获取该类的父类

新建一个People类,并让User继承People:

class People {

}
class User extends People{

测试代码:

Class<?> superclass = userCls.getSuperclass();
System.out.println(superclass);

运行结果:

class com.chenshu.test.People

  1. getInterfaces() 获取该类的所有接口

先编写两个接口,并让User类implement它们:

interface IA {
    
}
interface IB{
    
}
class User extends People implements IA, IB{

测试代码:

Class<?>[] interfaces = userCls.getInterfaces();
for(Class anInterface : interfaces) {
    System.out.println("接口信息:" + anInterface);
}

运行结果:

接口信息:interface com.chenshu.test.IA
接口信息:interface com.chenshu.test.IB

  1. getAnnotations() 获取该类的所有注解

随便写一个注解,用于测试:

@Resource
class User extends People implements IA, IB{

测试代码:

Annotation[] annotations = userCls.getAnnotations();
for (Annotation annotation : annotations) {
    System.out.println("注解信息:" + annotation);
}

运行结果:

注解信息:@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)

6. 反射暴力破解

前面提到的所有API,都只能获取到 公有的字段、方法、构造器,想要通过反射来获得私有的字段、方法、构造器就必须得用到暴力破解

暴力破解指的就是使用setAccessible()这个方法,在这个方法中可以传truefalse两种参数,分别代表禁用或启用安全检查,如果禁用安全检查,就可以不受修饰符的影响,随意实例化、调用方法、修改和获取属性,接下来我们来学习如何利用反射机制暴力破解。

创建一个User类用于测试:

class User {
    private int age;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                '}';
    }

    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    private void userMethod() {
        System.out.println("this is a private method..");
    }
}

6.1 暴力创建实例

上面的User类只有一个private修饰的构造方法,通过new的方式无法创建实例,但是如果通过反射机制可以暴力破解。

测试代码:

Class userClazz = Class.forName("com.chenshu.vialate.User");
//获取私有构造器
Constructor declaredConstructor = userClazz.getDeclaredConstructor(int.class, String.class);
//暴破,使用反射可以访问private构造器
declaredConstructor.setAccessible(true);
Object user = declaredConstructor.newInstance(20, "zhangsan");
//打印user
System.out.println(user);

运行结果:

User{age=20, name='zhangsan'}

6.2 暴力破解属性

同样的,没有set方法是无法修改private修饰的属性的,但是通过反射,可以暴力破解。

测试代码:

Field age  =userClazz.getDeclaredField("age");
age.setAccessible(true);
age.set(user, 99);
System.out.println(user);

我们可以发现age字段成功被修改:

User{age=99, name='zhangsan'}

6.3 暴力使用方法

最后,在出了User类这个作用域后,无法使用private修饰的方法,但是通过反射机制,一切都是浮云。

测试代码:

Method method = userClazz.getDeclaredMethod("userMethod");
method.setAccessible(true);
method.invoke(user);

运行结果:

this is a private method..

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538546.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Scala实战:打印九九表

本次实战的目标是使用不同的方法实现打印九九表的功能。我们将通过四种不同的方法来实现这个目标&#xff0c;并在day02子包中创建相应的对象。 方法一&#xff1a;双重循环 我们将使用双重循环来实现九九表的打印。在NineNineTable01对象中&#xff0c;我们使用两个嵌套的fo…

sql注入之宽字节注入

1.1 宽字节注入原理 宽字节注入&#xff0c;在 SQL 进行防注入的时候&#xff0c;一般会开启 gpc&#xff0c;过滤特殊字符。 一般情况下开启 gpc 是可以防御很多字符串型的注入&#xff0c;但是如果数据库编码不 对&#xff0c;也可以导致 SQL 防注入绕过&#xff0c;达到注入…

7、Qt--QLabel使用小记

前言&#xff1a;QLabel作为QT中基础的控件&#xff0c;功能简单使用方便频繁&#xff0c;主要用于显示文本、图片等信息。笔者这里记录下一些开发使用心路&#xff0c;方便小白快速入手。 一、添加背景图片 首先需要在资源中添加好图片资源&#xff0c;图片资源的添加参考4.1…

ArcGIS Desktop使用入门(三)图层右键工具——组织要素模板

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

每日一练(力扣)

我的思路是暴力枚举: 情况1:相同&#xff0c;就让子串和原串同时后移继续比较 情况2:不相同&#xff0c;就只让原串后移 public int strStr(String haystack, String needle) {if (haystack.length() < needle.length()){return -1;}for (int i 0; i < h…

PointNet++函数square_distance(src, dst):计算两组点之间的欧式距离(代码详解)

文章目录 一、计算两组点之间的欧式距离二、举例三、中间结果输出 一、计算两组点之间的欧式距离 def square_distance(src, dst):"""Calculate Euclid distance between each two points.src^T * dst xn * xm yn * ym zn * zm&#xff1b;sum(src^2, dim-1…

Qt | 对象树与生命期(对象的创建、销毁、擦查找)

一、组合模式与对象树 1、组合模式指的是把类的对象组织成树形结构,这种树形结构也称为对象树,Qt 使用对象树来管理 QObject 及其子类的对象。注意:这里是指的类的对象而不是类。把类组织成树形结构只需使用简单的继承机制便可实现。 2、使用组合模式的主要作用是可以通过…

库、框架、脚手架和IDE一文讲明白

在区分上面几个问题前&#xff0c;咱们先看看几个疑问。 一、常见问题汇总 js css直接复制到服务器 然后引用不就行了么&#xff1f; 为什么还需要安装&#xff1f; 引入js不就是引入了框架了吗&#xff1f;框架就是js&#xff1f; 脚手架和框架都有架&#xff0c;是不是一…

Python | 月平均气候态 | SST

数据来源&#xff1a; NOAA Optimum Interpolation (OI) SST V2 下载地址&#xff1a; https://psl.noaa.gov/data/gridded/data.noaa.oisst.v2.html 空间分辨率: 5.0 degree latitude by 5.0 degree longitude global grid (72x36)87.5N,覆盖范围 01/1856 to 2023/0187.5…

每日一题 — 水果成篮

思路&#xff1a; 通过阅读上面文字得出问题&#xff1a;就去只有两个种类的最大长度的连续子数组&#xff0c;这时我们可以想到用哈希表来存储数据&#xff0c;记录数据的种类和每个种类的数量。 解法一&#xff1a;暴力递归&#xff08;right每次遍历完都回退&#xff09; 解…

十五届web模拟题整理

模拟赛一期 1.动态的Tab栏 请在 style.css 文件中补全代码。 当用户向下滚动的高度没有超过标题栏&#xff08;即 .heading 元素&#xff09;的高度时&#xff0c;保持 Tab 栏在其原有的位置。当滚动高度超过标题栏的高度时&#xff0c;固定显示 Tab 栏在网页顶部。 /* TODO…

01_QT编译报错:Cannot find file:问题解决

QT编译报错&#xff1a;Cannot find file:问题解决 报错原因&#xff1a;创建路径存在中文字符&#xff0c;将文件路径改为英文字符即可

异地两分部子网重复,如何远程更改其中一个分部子网信息

环境: 分部1:子网192.168.1.0/24 分部2:子网192.168.1.0/24 问题描述: 异地两分部子网重复,如何远程更改其中一个分部子网,原本没有问题目前要与总部建ipsec提示冲突无法都建立隧道 解决方案: 先G一下,看看有啥建议 在两个异地分部网络中,如果发现有子网地址出现…

电脑文件名乱码,数据恢复有高招!

在日常使用电脑的过程中&#xff0c;突然遭遇文件名乱码的情况&#xff0c;确实让人头疼不已。原本井井有条的文件目录&#xff0c;一下子变得杂乱无章&#xff0c;文件名变成了一堆无意义的乱码字符。这种情况不仅影响了文件的正常使用&#xff0c;还可能导致重要数据的丢失。…

【C++】拆分详解 - 内存管理

文章目录 前言一、C/C内存分布二、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free三、C内存管理方式  3.1 new/delete操作内置类型  3.2 new和delete操作自定义类型  3.3 operator new与operator delete函数 四、new和delete的实现原理  4.1 内置类型…

智能助力:大模型自动填写工单准确率达95%

基于大模型优秀的问答、总结和话术生成能力&#xff0c;主流联络中心纷纷接入大模型升级智能知识库、智能工单、智能陪练等应用。 以智能填单为例&#xff0c;借助大模型能够轻松从对话中提取出实体信息、判定对话意图、识别情绪、生成沟通摘要等。通过简单的Prompt&#xff0c…

鸿蒙TypeScript学习第13天:【元组】

1、TypeScript 元组 我们知道数组中元素的数据类型都一般是相同的&#xff08;any[] 类型的数组可以不同&#xff09;&#xff0c;如果存储的元素数据类型不同&#xff0c;则需要使用元组。参考文档&#xff1a;qr23.cn/AKFP8k 元组中允许存储不同类型的元素&#xff0c;元组…

三方库移植之NAPI开发[4]异步调用:CallbackPromise

写在开头&#xff1a; 本文在 三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI 的基础上修改hellonapi.cpp、index.ets&#xff0c;接着学习NAPI异步模型的Promise、Callback方式。本文共有三个示例&#xff0c;分别是Callback 异步接口示例、Promise 异步接口示例、规范异步…

【算法每日一练]-图论(lca) 最近公共祖先LCA,货车运输

目录 P3379&#xff1a;最近公共祖先LCA 思路&#xff1a; 货车运输 P3379&#xff1a;最近公共祖先LCA 思路&#xff1a; 首先进行的预处理&#xff0c;将所有点的深度和p数组求出来 设置&#xff1a;p[i][j]存的从i向上走2的j次方那么长的路径到的父节点 更深的点走到和…

STM32学习和实践笔记(8): 理解位带区和位带别名区

如前《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (b)&#xff08;含memory mapping图&#xff09;-CSDN博客 》中所写&#xff0c; STM32一共有4GB的地址&#xff0c;对所有的寄存器、存储器、外设等进行统一编址。 每…