Java进阶_反射

[TOC]
反射概念
基本概念
框架:半成品软件,可以在框架的基础上进行软件开发,简化编码。
学习框架并不需要了解反射,但是要是想自己写一个框架,那么就要对反射机制有很深入的了解。
反射机制:将类的各个组成部分封装为其他对象,这就是反射机制。反射的好处:
可以在程序运行过程中,操作这些对象。
可以解耦,提高程序的可扩展性。
假设我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,以导致整个项目无法启动。
对于反射创建对象Class.forName("全类名");这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。这就是Spring框架中
IOC控制反转的本质。
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。
我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
代码的三个阶段
Source源代码阶段:*.java被编译成*.class字节码文件。
Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。
RunTime运行时阶段:创建对象的过程new。
img
Java内存
堆: 存放new的对象和数组,被所有的线程共享,不会存放别的对象引用
栈: 存放基本变量类型(会包含这个基本类型的具体数值),引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区: 可以被所有的线程共享,包含了所有的class和static变量
类加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
类加载与ClassLoader
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 初始化:执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
类构造器是构造类信息的,不是构造该类对象的构造器。 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
类初始化时机
类主动引用(一定会发生类的初始化)当虚拟机启动,先初始化main方法所在的类
new一个类的对象
调用类的静态成员(除了final常量)和静态方法
使用java.lang.reflect包的方法对类进行反射调用
当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类被动引用(不会发生类的初始化)当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类引用,不会触发此类的初始化
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
类加载器的作用
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
类加载器作用是用来把类(class)装载进内存的。
JVM规范定义了如下类型的类的加载器。
自定义网络类加载
Class对象获取方式
获取Class对象的
三种方式对应着java代码在计算机中的三个阶段
Source源代码阶段: Class.forName("全类名"):将字节码文件加载进内存,返回Class对象多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
Class类对象阶段: 类名.class:通过类名的属性class获取多用于参数的传递
Runtime运行时阶段: 对象.getClass():getClass()方法是定义在Objec类中的方法多用于对象的获取字节码的方式
结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。
Class clazz=Person.class;
已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz=person.getClass();
已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz=Class.forName("demo01.Student");
内置基本数据类型可以直接用类名.Type
还可以利用ClassLoader我们之后讲解
哪些类型可以有Class对象?
class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
interface: 接口
[]: 数组
enum: 枚举
annotation: 注解@interface
primitive type: 基本数据类型
void
反操作泛型
Method和Field、Constructor对象都有setAccessible)方法。
setAccessible作用是启动和禁用访问安全检查的开关。
参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
使得原本无法访问的私有成员也可以访问
参数值为false则指示反射的对象应该实施Java语言访问检查
Class对象功能
获取功能
1)获取成员变量们
2)获取构造方法们
3)获取成员方法们
4)获取全类名和简单类名
Field:成员变量
设置值: void set(Object obj, Object value)
获取值: get(Object obj)
忽略访问权限修饰符的安全检查 setAccessible(true):
暴力反射
没有忽略访问修饰符直接访问抛出的异常
Constructor:构造方法
创建对象:T newInstance(Object... initargs)
注意:如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
获取构造方法们
Constructor<?>[] getConstructors()
Constructor getConstructor(类<?>... parameterTypes) */ @Test public void reflect4() throws Exception { Class personClass = Person.class;
//Constructor<?>[] getConstructors() Constructor[] constructors = personClass.getConstructors(); for(Constructor constructor : constructors){ // Constructor 对象reflect包下的 import java.lang.reflect.Constructor; System.out.println(constructor); } System.out.println("==========================================");
// 获取无参构造函数 注意:Person类中必须要有无参的构造函数,不然抛出异常 Constructor constructor1 = personClass.getConstructor(); System.out.println("constructor1 = " + constructor1); // 获取到构造函数后可以用于创建对象 Object person1 = constructor1.newInstance(); // Constructor类内提供了初始化方法newInstance();方法 System.out.println("person1 = " + person1); System.out.println("==========================================");
// 获取有参的构造函数 // public Person(String name, Integer age) // 参数类型顺序要与构造函数内一致,且参数类型为字节码类型 Constructor constructor2 = personClass.getConstructor(String.class,Integer.class); System.out.println("constructor2 = " + constructor2);
// 创建对象 Object person2 = constructor2.newInstance("张三", 23); // 获取的是有参的构造方法,就必须要给参数 System.out.println(person2); System.out.println("=========================================");
// 对于一般的无参构造函数,我们都不会先获取无参构造器之后在进行初始化。 // 而是直接调用Class类内的newInstance()方法 Object person3 = personClass.newInstance(); System.out.println("person3 = " + person3); // 我们之前使用的 Class.forName("").newInstance // 其本质上就是调用了类内的无参构造函数来完成实例化的 // 以后在使用 Class.forName("").newInstance; 反射创建对象时,一定要保证类内有无参构造函数 }
对于getDeclaredConstructor方法和getDeclaredConstructors方法
getDeclaredConstructor方法可以获取到任何访问权限的构造器,而getConstructor方法只能获取public修饰的构造器。此外在构造器的对象内也有setAccessible(true);方法,并设置成true就可以操作了。
关于为什么要使用private访问权限的构造器,使用这个构造器不就不能外部访问了嘛,不也就无法进行实例化对象了吗?无法在类的外部实例化对象正是私有构造器的意义所在,在单例模式下经常使用,整个项目只有一个对象,外部无法实例化对象,可以在类内的进行实例化并通过静态方法返回(由于实例化的对象是静态的,故只有一个对象,也就是单例的)。
网上说这就是单例模式中的饿汉模式,不管是否调用,都创建一个对象。
Method:方法对象
执行方法:Object invoke(Object obj, Object... args)
获取方法名称:String getName();
getName方法
getName()方法获取的方法名是仅仅就是方法名(不带全类名),且不带有参数列表。
获取类名
getClass()方法是Object类的方法,需要注意一点获取的类名是全类名(带有路径)
关于获取成员方法们的另外两个方法
同之前的叙述一样,带有Declared关键字的方法这两个方法,可以获取到任意修饰符的方法。同样的提供了setAccessible(true);方法进行暴力反射。
Method和Field、Constructor对象都有setAccessible)方法。
setAccessible作用是启动和禁用访问安全检查的开关。
参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
使得原本无法访问的私有成员也可以访问>参数值为false则指示反射的对象应该实施Java语言访问检查
综上说述:对于反射机制来说,在反射面前没有公有私有,都可以通过暴力反射解决。
Last updated
Was this helpful?














