Java 反射及原理

article/2025/9/21 4:13:31

反射,指的是对于任意一个类,都可以动态的获得它的所有属性和方法,对于任意一个对象都能调用的它的所有属性和方法,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

想要理解反射首先我们要知道JVM也就是java的虚拟机,java能够跨平台也是因为它,JVM说白了也就是一个进程,只不过是用来跑你的代码的。
在这里插入图片描述
上图是java的内存模型,我们关注的点,一个方法区,一个栈,一个堆,初学的时候老师不深入的话只告诉你java的内存分为堆和栈,易懂点吧!

加载一个类主要有三个阶段:编译加载、连接、初始化
假如你写了一段代码 Object o=new Object()
首先你的代码会被编译成一个.class字节码文件,然后这个字节码文件会被类加载器也就是ClassLoader加载到内存中,并且创建一个class对象(每一个类都会有一个且只有一个class对象)。加载器 ClassLoader 加载class文件时,会把类里的一些数值常量、方法、类信息等加载到内存中,称之为类的元数据,最终目的是为了生成一个Class对象用来描述类,这个对象会被保存在.class文件里。

一个类的元数据会被转换成方法区中的运行时数据
成员变量---->Field类的对象,可以有多个,所以Field[]
构造器---->Constructor类的对象,可以有多个,所以Constructor[]
方法---->Method类的对象,可以有多个,所以Method[]
在这里插入图片描述
编译完成之后会来到连接阶段,将java类的二进制代码合并到JVM的运行状态中的过程,它会做这几件事

  • 验证:验证你的代码是否有问题,是否有安全方面的问题
  • 准备:将类变量进行分配内存并初始化,这些内存是在方法区中分配的
  • 解析:将常量池中的符号引用替换成直接引用,在编译的时候每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替,而在解析阶段就是为了把这个符号引用转化成真正的地址的阶段。

接下来就是初始化阶段了

  • 执行类构造器clinit,类构造器是由编译阶段自动收集所有类变量的复制动作和静态代码块中的动作语句合并产生的(类构造器是构造类信息的,不是该类的构造器)
  • 初始化一个类的时候会首先去检查父类是否初始化,如果没有会首先初始化父类
  • jvm会保证一个类的clinit方法在多线程环境下被争取的加锁和同步

那么什么时候会发生类的初始化呢?

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量),和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有初始化则先初始化父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如:当通过子类引用父类的静态变量时,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化,如 int[] arr=new int[]
  • 引用常量不会发生此类的初始化,常量在编译阶段就存入常量池了

类加载器
类加载器的作用:将class字节码文件加载到内存中,并将这些静态数据装换成方法区的运行时数据,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口

类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某一个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过GC垃圾回收机制可以回收这些Class对象

JVM规范定义了如下类型的类加载器

  • 类加载器Bootstrap classLoader:由C++编写,是JVM自带的类加载器,负责java平台核心库,用来装载核心类库,该加载器无法直接获取
  • 扩展加载类加载器ExtClassLoader:负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
  • 系统加载器AppClassLoader:负责java-classpath或-D java.class.path所指的目录下的类于jar包装入工作,是最常用的加载器

双亲委派机制
当我们需要加载一个类的时候,首先AppClassLoader会检查自己是否加载过,如果加载过则无需加载,否则就会拿到父加载器,父加载器同样会进行检查,一直到Bootstrap classLoader,而不是先看自己是否能加载,到了Bootstrap classLoader之后,如果Bootstrap classLoader不能加载,则会下沉到子加载器,如果可以则加载,如果不可以则继续下沉,一直到AppClassLoader,如果还是无法加载,则抛出异常ClassNotFoundException

我们能够获取的运行时的class类的完整结构结构

  • 所有的属性Field
  • 所有的构造器Constructor
  • 所有的方法Method
  • 所继承的父类SuperClass
  • 实现的全部接口Interface
  • 注解Annotation
  • 名字Name

有两种类型获取,当然前提是你有这个类

  • get+你要获取的东西,例如:获取属性为getField()、获取方法为getMethod()
  • get+Declared+你要获取的东西,例如:获取属性为getDeclaredField()、获取方法为geDeclaredtMethod()

那么这两种方式有什么区别了,区别在于第一种方法只能获取公有的东西,而第二种是获取全部
需要注意的是获取方法的时候,getMethod可以获取到本类及其父类的方法,而getDeclaredMethod只能获取到本类

如何去调用方法和修改属性
方法.invoke(类对象)

Class c1 = Class.forName("experiment.B");
B b = (B) c1.getConstructor(null).newInstance();
Method method=c1.getDeclaredMethod("A",null);
method.invoke(b);

属性.set(类对象,设置成什么值)

Class c1 = Class.forName("experiment.B");
B b = (B) c1.getConstructor(null).newInstance();
Field field=c1.getDeclaredField("name");
field.set(b,"orange");
System.out.println(field.get(b));

newInstance()和new()
JAVA9之后的版本请使用构造器创建对象,class.getDeclaredConstructor().newInstance()

  • 1、两者创建对象的方式不同,前者是实用类的加载机制,后者则是直接创建一个类:
  • 2、newInstance创建类是这个类必须已经加载过且已经连接,new创建类是则不需要这个类加载过
  • 3、newInstance: 弱类型(GC是回收对象的限制条件很低,容易被回收)、低效率、只能调用无参构造,new 强类型(GC不会自动回收,只有所有的指向对象的引用被移除是才会被回收,若对象生命周期已经结束,但引用没有被移除,经常会出现内存溢出)

需要注意的是使用newInstance()必须满足两个条件:1.这个这个类已经加载了。2.这个类已经链接

下面说说如何获取泛型信息
getGenericParameterTypes 获取所有泛型参数信息
getGenericReturnType 获取泛型返回值信息(返回值只有一个)
getGenericExceptionTypes 获取所有泛型错误信息

ParameterizedType 真实参数

public class B {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {Class c1 = Class.forName("my.genericity");Method method = c1.getMethod("aaa", Map.class);Type[] genericParameterTypes = method.getGenericParameterTypes();for (Type genericParameterType : genericParameterTypes) {System.out.println(genericParameterType);if(genericParameterType instanceof ParameterizedType){Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();for (Type actualTypeArgument : actualTypeArguments) {System.out.println(actualTypeArgument);}}}}
}

泛型还可以越过泛型检查,请看下面

public class B {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {ArrayList<Integer> arr=new ArrayList<>();Class<? extends ArrayList> aClass = arr.getClass();Method add = aClass.getMethod("add", Object.class);add.invoke(arr,"hello");add.invoke(arr,"world");add.invoke(arr,"orange");System.out.println(arr);}
}

运行结果:
在这里插入图片描述
我们定义了一个Integer类型的集合,但是我们居然可以存String类型的数据,这就是反射牛逼之处的体现

通过配置文件来指定内容

public class B {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException {Properties prop=new Properties();FileReader fr=new FileReader("data.txt");prop.load(fr);fr.close();String className = prop.getProperty("ClassName");String methodName = prop.getProperty("MethodName");Class<?> aClass = Class.forName(className);Constructor<?> con1 = aClass.getConstructor(String.class);Object o = con1.newInstance("libo");Method method = aClass.getMethod(methodName);System.out.println(method.invoke(o));}
}

如有不对欢迎联系改正!!!感谢阅读


http://chatgpt.dhexx.cn/article/DONAIPBV.shtml

相关文章

回车、换行、回车换行、硬回车以及软回车

回车、换行、回车换行、硬回车以及软回车 要想一句话说清楚它们之间的关系&#xff0c;不太简单。但认真看完后&#xff0c;会发现它们之间的关系其实也挺简单的。 回车、换行与回车换行 英文缩写对应按键英文全称中文名称解释转义表达式使用平台CRreturnCarriage Return回车…

图解回车和换行的区别

文章目录 1. 定义2. 图解3. 讨论4. 结论 1. 定义 中文英文简写HEXCharacterASCII回车Carriage returnCR0x0D\r13换行Line feedLF0x0A\n10 2. 图解 Win11 Experiment by Pycharm with Python 3.9 print(---)print(Hello World)print(---)# \rprint(Hello \r World)print(---…

不同系统下回车和换行的区别

在计算机还没有出现之前&#xff0c;有一种叫做电传打字机&#xff08;Teletype Model 33&#xff09;的玩意&#xff0c;每秒钟可以打10个字符。但是它有一个问题&#xff0c;就是打完一行换行的时候&#xff0c;要用去0.2秒&#xff0c;正好可以打两个字符。要是在这0.2秒里面…

python回车和换行的区别_回车与换行的区别(转)

add by zhj: 不同操作系统下换行符不同&#xff0c;如下&#xff1a; \n: UNIX \n\r: window \r: MAC OS 我们经常遇到的一个问题就是&#xff0c;Unix/Mac系统下的文件在Windows里打开的话&#xff0c;所有文字会变成一行&#xff1b;而Windows里的文件在Unix/Mac下打开的…

回车与换行的区别,CRLF、CR、LF详解(\r \n \r\n的区别)

先上结论 缩写ASCⅡ转义系统ASCⅡ值CR\rMacIntosh&#xff08;早期的Mac&#xff09;13LF\nUnix/Linux/Mac OS X10CR LF\r\nWindows 很长一段时间里&#xff0c;对于CRLF、CR、LF的理解仅限于不同操作系统下对换行符的定义。所谓知其然需知其所以然&#xff0c;从学习中找到乐…

回车符,换行符的区别

首先介绍一下“回车”&#xff08;carriage return,’\r’&#xff09;和“换行”&#xff08;line feed,’\n’&#xff09;这两个概念的来历和区别。在计算机还没有出现之前&#xff0c;有一种叫做电传打字机&#xff08;Teletype Model 33&#xff09;的玩意&#xff0c;每秒…

换行和回车的区别

我们在看他们的区别时我们先看看他们的分别指的是什么&#xff1a; 回车<\r>(carriage return)&#xff1a;告诉打印机把打印头定位到左边界&#xff0c;就是指的&#xff0c;那个打印头重新放在这一行的开始。 换行<\n>(line feed)&#xff1a;告诉打印机把打印头…

回车符和 换行符的区别

首先介绍一下“回车”&#xff08;carriage return,’\r’&#xff09;和“换行”&#xff08;line feed,’\n’&#xff09;这两个概念的来历和区别。在计算机还没有出现之前&#xff0c;有一种叫做电传打字机&#xff08;Teletype Model 33&#xff09;的玩意&#xff0c;每秒…

回车符、换行符和回车换行符

我的CSDN主页My Python 学习个人备忘录我的HOT博 自学并不是什么神秘的东西&#xff0c;一个人一辈子自学的时间总是比在学校学习的时间长&#xff0c;没有老师的时候总是比有老师的时候多。             ——华罗庚 \r 、\n 和 \r\n 题目代码运行效果我的解题思路…

回车符与换行符的区别

引用了博客&#xff1a;https://blog.csdn.net/fanwenbo/article/details/54848429 这里是我自己做的实验&#xff0c;如下&#xff1a; printf("A is here!");printf("I am here!");putchar(\r);printf("First input!");putchar(\n);printf(&qu…

“回车”、“换行”浅谈

“回车”、“换行”浅谈 转义字符\r表示回车&#xff08;carriage return&#xff09;即回到行首&#xff0c;并没有包含换行的动作&#xff0c; \n表示换行&#xff08;line feed&#xff09;即移动到新的一行&#xff08;下一行&#xff09;。顺便提及&#xff0c;\b表示退格…

回车与换行符的区别及python中使用

一、区别 转载自http://www.pythontab.com/html/2017/linuxkaiyuan_0115/1116.html 1. 由来 在计算机还没有出现之前&#xff0c;有一种叫做电传打字机&#xff08;Teletype Model 33&#xff09;的机械打字机&#xff0c;每秒钟可以打10个字符。但是它有一个问题&#xff0c…

回车和换行的区别

回车和换行的区别 回车和换行的概念不同的系统间传递文件会涉及格式的转换Unix -> WindowsUnix <- Windows 回车和换行的概念 首先介绍一下“回车”&#xff08;carriage return,’\r’&#xff09;和“换行”&#xff08;line feed,’\n’&#xff09;这两个概念的来历…

在数组中查找指定元素 (15分)

int search( int list[], int n, int x ){int index -1;for(int i0;i<n;i){if(list[i] x){index i;break;}}return index; }

[PTA]习题8-2 在数组中查找指定元素

Spring-_-Bear 的 CSDN 博客导航 本题要求实现一个在数组中查找指定元素的简单函数。 函数接口定义&#xff1a; int search( int list[], int n, int x );其中 list[] 是用户传入的数组&#xff1b;n&#xff08;≥0&#xff09;是 list[] 中元素的个数&#xff1b;x 是待查…

[PTA]实验8-1-5 在数组中查找指定元素

Spring-_-Bear 的 CSDN 博客导航 本题要求实现一个在数组中查找指定元素的简单函数。 函数接口定义&#xff1a; int search( int list[], int n, int x );其中 list[] 是用户传入的数组&#xff1b;n&#xff08;≥ 0&#xff09;是 list[] 中元素的个数&#xff1b;x 是待…

习题8-2 在数组中查找指定元素

习题8-2 在数组中查找指定元素 (15 分) 本题要求实现一个在数组中查找指定元素的简单函数。 函数接口定义&#xff1a; int search( int list[], int n, int x );其中list[]是用户传入的数组&#xff1b;n&#xff08;≥0&#xff09;是list[]中元素的个数&#xff1b;x是待…

C语言刷题系列——9.在数组中查找指定元素

&#x1f6a9;实现一个在数组中查找指定元素的简单函数 &#x1f506;一) 题目要求&#x1f506;二) 题解 &#x1f506;一) 题目要求 函数接口定义&#xff1a;int search( int list[], int n, int x );其中list[]是用户传入的数组&#xff1b;n&#xff08;≥0&#xff09;是…

逻辑回归L1和L2正则化

正则化 正则化是用来防止模型过拟合的过程&#xff0c;常用的有L1正则化和L2正则化两种选项&#xff0c;分别通过在损失函数后加上参数向 量 的L1范式和L2范式的倍数来实现。这个增加的范式&#xff0c;被称为“正则项”&#xff0c;也被称为"惩罚项"。损失函数改变&…

【机器学习】L1正则化和L2正则化

L1正则化和L2正则化 在机器学习实践过程中&#xff0c;训练模型的时候往往会出现过拟合现象&#xff0c;为了减小或者避免在训练中出现过拟合现象&#xff0c;通常在原始的损失函数之后附加上正则项&#xff0c;通常使用的正则项有两种&#xff1a;L1正则化和L2正则化。L1正则…