反射
Java是面向对象的,有对象必须先有类, 有static修饰类的属性和方法;在Java中存储了类的内容,这个内容也应该是一个对象;Java中每一个用到的类都会加载一块内存,这每一块内存都是一个对象;这些对象记录了这些类中声明了哪些属性和方法以及构造方法,Java将这类抽象为一个Class类。
类对象
类的类对象中存储了类中定义的内容:属性、方法、构造方法
Class类的对象是不能new的。
获取类对象的方式有三种:
1.通过类名获取类对象
//通过类名获取类对象Class clazz = EasyClassA.class;
2.通过对象获取类对象
//通过对象获取类对象clazz = new EasyClassA().getClass();
3.通过Class类的forName方法获取
//通过Class类的forName方法获取clazz = Class.forName("com.easy725.EasyColor");
Field 类:字段
在Java中用Field类来记录类的属性
获取类中的public声明的公有属性:getField、getFields
c=Class.forName("com.easy726.Easy"); //获取类的属性//fName变量指向的对象就是Easy类中的name属性Field fName = c.getField("name");//传入属性名获取公共的属性
获取属性的值 get
//可以获取某一个Easy类的对象的name属性的值Object objectName = fName.get(easy);System.out.println(objectName+"---反射");
设置属性的值 Set
//注入该属性的值fName.set(easy,"李四");System.out.println(easy.name);
获取类中声明的任意属性:getDeclaredField
Field fSex = c.getDeclaredField("sex");//默认访问权限Field fAddress = c.getDeclaredField("address");//私有private访问权限
访问私有属性必须先获取访问权限:setAccessible(true)
//反射访问私有属性 必须先获取访问权限fAddress.setAccessible(true);//把访问权限设置为truefAddress.set(easy,"青岛市城阳区");System.out.println(fAddress.get(easy));
什么是反射?
在Java中,反射是指程序在运行时动态地检测、获取和操作类、对象、方法和属性的能力。通过反射,我们可以在运行时获取任意一个类的信息,包括类的名称、方法、字段等,并且可以在运行时创建对象、调用方法和访问属性。
反射就是在程序运行期间,可以动态的获取类中定义的属性和方法以及构造方法的机制(思想)的实现
反射的核心是 Class类 ,程序中使用的每一个类,都有一个唯一的对应的Class对象
反射的API:能够调用的属性和方法 Field、Method、Constructor
API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。
反射会破坏类的封装性,需要通过场景说明,反射和封装没有绝对的好坏,只是应用场景不同
通过反射创建对象
Easy easy = c.newInstance();//调用类的无参构造方法创建对象
下面演示了一个通过反射创建对象的案例:利用泛型写一个方法,根据读取到的Map中的键值对,通过反射创建对象并为对应属性赋值。
public static <T> T getInstance(Class<T> tClass, Map values) throws InstantiationException, IllegalAccessException {//通过反射获取实例 创建对象T t = tClass.newInstance();//通过类中的无参构造方法创建对象//通过反射获取类中定义的属性Field[] farr = tClass.getDeclaredFields();//获取类中声明的所有属性//System.out.println(Arrays.toString(farr));for (Field fItem:farr){//获取属性的名字String fName = fItem.getName();//获取该属性在Map中的键值对 属性对应的值Object value = values.get(fName);//设置属性访问权限fItem.setAccessible(true);fItem.set(t,value);//把t对象的fItem属性的值设置为value}return t;//返回对象t}public static void main(String[] args) throws InstantiationException, IllegalAccessException {Map map = new HashMap();map.put("code","C1001");map.put("name","张三");map.put("sex","女");Student stu = getInstance(Student.class,map);System.out.println(stu);}
Method 类:方法
在前面的Easy类中,已经写好了一个无参和一个有两个int类型参数的成员方法
public void methodA(){System.out.println("methodA");}public void methodB(int a, int b){System.out.println("methodB");System.out.println("两个参数分别为:"+a+","+b);}
先通过类对象的 newInstance() 方法创建类的实例
//获取类对象Class<Easy> c=Easy.class;Easy easy = c.newInstance();//创建实例
反射获取public方法:getMethod ( 传入方法名和参数列表 )
//获取public方法,传入方法名和参数列表Method ma=c.getMethod("methodA");Method mb=c.getMethod("methodB", int.class, int.class);
面向对象的思想调用成员方法: 对象.方法名()
在反射中用 invoke ( 对象,参数 ) 驱动对象执行方法
ma.invoke(easy);mb.invoke(easy,23,45);
Constructor 类:构造方法
前面提到,可以通过类对象的newInstance()方法直接调用无参构造方法创建实例,因而推荐每个类都要有一个无参的构造方法
//反射获取构造方法Class<Easy> c= Easy.class;c.newInstance();//调用无参的构造方法
也可以通过 getConstructor() 方法直接获取一个构造方法的对象,因为构造函数名有约束,必须和类名相同,获取指定的构造函数只需要传入参数列表即可。
Constructor<Easy> con = c.getConstructor();con.newInstance();//构造方法是谁就调用谁con = c.getConstructor(String.class);con.newInstance("张三");
Modifier 类:修饰符
使用Modifier的方法判断方法/属性/构造方法的修饰符
Modifier类将不同的修饰符定义为 int 类型的16进制整型常量;
相当于二进制的每一位都对应一个特定的修饰符
判断时只需要把获取的修饰符对应的整数,进行按位与运算,只要结果不为0,就说明有对应二进制位置的修饰符修饰:
//修饰符 使用Modifier的方法判断方法/属性/构造方法的修饰符Field f = c.getDeclaredField("test");int fmod = f.getModifiers();//判断是否使用了某个修饰符Boolean bool = Modifier.isStatic(fmod);// 1 0 0 0System.out.println(fmod);// 1 0 0 1 1 0 0 1 按位与运算不为 0System.out.println(bool);System.out.println(Modifier.toString(fmod));
内省
内省(Introspection)是一种通过反射机制来获取和操作类的属性、方法和事件的方式。
底层还是通过反射实现的,内省不会破坏封装性
通过获取属性的读方法和写方法(getter/setter)来获取和设置属性的内容,不会破坏类的封装性
BeanInfo bi = Introspector.getBeanInfo(c);//只能获取setter和getter方法bi.getBeanDescriptor();PropertyDescriptor[] pds = bi.getPropertyDescriptors();//获取属性描述String pName = pds[0].getName();//获取属性的名字Method read = pds[0].getReadMethod();//对应属性的getter方法Method write = pds[0].getWriteMethod();//对应属性的setter方法System.out.println(pName);Easy easy = c.newInstance();write.invoke(easy,"张三");
补充
synchronized同步机制的底层原理:
synchronized在JVM内部是通过Monitor(监视器)来实现的,Monitor是一种同步机制,它依赖于底层操作系统的Mutex Lock(互斥锁)来实现。Monitor与Java对象相关联,当线程尝试进入synchronized修饰的方法或代码块时,它会尝试获取与该对象关联的Monitor的锁。
Monitor的获取与释放:当线程进入synchronized块时,它会执行一个monitorenter指令,尝试获取对象关联的Monitor的锁。如果Monitor处于无锁状态,该线程会成功获取锁,并在对象头中标记为已锁定。如果Monitor已被其他线程锁定,则进入该方法的线程将被阻塞,直到获取到Monitor的锁。当线程退出synchronized块时,它会执行monitorexit指令,释放Monitor的锁,并允许其他等待的线程获取锁。
在JVM中,每个Java对象在内存中都有特定的结构,通常分为三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。其中,对象头是实现synchronized同步机制的关键部分。
对象头:对象头包含了对象的元数据信息,如哈希码(Hash Code)、GC分代年龄、锁状态标志、偏向线程ID、偏向时间戳等。这部分信息在JVM中用于支持对象的同步机制。
JVM(Java Virtual Machine)生命周期
JVM的生命周期可以概括为以下几个阶段:
1. 加载(Loading)
- 将Java字节码文件(.class文件)从磁盘加载到JVM的内存中,并创建对应的
Class
对象,为程序的执行准备必要的类信息。
2. 链接(Linking)
- 验证(Verification):检查加载的字节码是否符合JVM规范,确保没有违反安全性的操作,防止执行恶意代码或包含错误的字节码。
- 准备(Preparation):为类的静态变量分配内存空间,并赋予其Java语言规范中定义的默认值(如int为0,boolean为false等),为类的初始化做准备。
- 解析(Resolution):将符号引用(如类名、字段名、方法名)转换为直接引用(即内存中的实际地址),确保在程序执行时能够快速地定位到类和成员。
3. 初始化(Initialization)
- 执行类构造器
<clinit>()
方法中的代码,包括静态变量的显式初始化和静态代码块的内容,完成类的初始化,准备类的实例创建和方法的调用。
4. 使用(Using)
- 在JVM运行时,根据程序逻辑执行方法调用、对象创建等操作,执行Java程序的逻辑功能。
5. 卸载(Unloading)
- 当类不再被JVM中的任何对象、线程或类加载器引用时,该类会被从内存中卸载,回收内存资源,防止内存泄漏。