当调用java
命令运行某个程序时,该命令将会启动一个Java虚拟机(Java Virtual Machine -> JVM)进程。同一个JVM的所有线程、变量都处于同一个进程里,他们都使用该JVM进程的内存区。
当Java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类加载指的是将类的class
文件读入内存,并为之创建一个java.lang.Class
对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class
对象。
通常有如下几种加载方式:
- 从本地文件系统加载
class
文件- 从
JAR
包加载class
文件- 通过网络加载
class
文件- 把一个
Java
源文件动态编译,并执行加载
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。
由于初始化步骤原因,JVM最先初始化的总是java.lang.Object
类
当使用ClassLoader
类的loadClass()
方法来加载某个类时,改方法只是加载该类,并不会执行该类的初始化。使用Class
的forName()
静态方法才会导致强制初始化该类。
每个类被加载之后,系统就会为该类生成一个对应的Class
对象,通过该Class
对象就可以访问到JVM中的这个类。一旦获得了某个类所对应的Class
对象之后,程序就可以调用Class
对象的方法来获得该对象和该类的信息了。
Class
类提供了大量的实例方法来获取该Class
对象所对应类的详细信息
下面4个方法用于获取Class
对应类所包含的构造器:
Connstructor<T> getConstructor(Class<?>... parameterTypes)
:返回此Class
对象对应类的、带指定形参列表的public
构造器
Connstructor<?>[] getConstructors()
:返回此Class
对象对应类的所有构造器
Connstructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
:返回此Class
对象对应类的、带指定形参列表的public
构造器,与构造器的访问权限无关
Connstructor<?>[] getDeclaredConstructors()
:返回此Class
对象对应类的所有构造器,与构造器的访问权限无关
下面4个方法用于获取Class
对应类所包含的方法:
Method getMethod(String name, Class<?>...parameterTypes)
:返回此Class
对象对应类的、带指定形参列表的public
方法
Method[] getMethods()
:返回此Class
对象所表示的类的所有public
方法
Method getDeclaredMethod(String name, Class<?>...parameterTypes)
:返回此Class
对象对应类的、带指定形参列表的方法,与方法的访问权限无关
Method[] getDeclaredMethods()
:返回此Class
对象所表示的类全部方法,与方法的访问权限无关
下面4个方法用于访问Class
对应类所包含的成员变量:
Field getField(String name)
:返回此Class
对象对应类的、指定名称的public
成员变量
Field[] getFields()
:返回此Class
对象对应类的所有public
成员变量
Field getDeclaredField(String name)
:返回此Class
对象对应类的、指定名称的成员变量,与方法的访问权限无关
Field[] getDeclaredFields()
:返回此Class
对象对应类的全部成员变量,与方法的访问权限无关
上面提到Class
对象可以获得该类里的构造器、方法和成员变量(由Field
对象表示),这三个类都位于java.lang.reflect
包下,并实现了java.lang.reflect.Member
接口。程序可以通过Method
对象来执行对应的方法,通过Constructor
对象来调用对应的构造器创建实例,通过Field
对象直接访问并修改对象的成员变量值。
调用Class
对象的newInstance()
方法即可创建一个Java
对象,如下,会输出当前日期
Class clazz = Class.forName("java.util.Date");
System.out.println(clazz.newInstance());
//等价于直接 System.out.println(new java.util.Date());
当获得某个类对应的Class
对象后,就可以通过该Class
对象的getMethods()
或getMethod()
方法来获取全部方法或指定方法。
每个Method
对象对应一个方法,获得Method
对象后,程序就可通过该Method
来调用它对应的方法。在Method
里包含一个invoke()
方法
Object invoke(Object obj, Object... args)
:该方法中的obj
是执行该方法的主调,后面的args
是执行该方法时传入该方法的实参
xxxxxxxxxx
Class clazz = Class.forName("java.util.Date");
System.out.println(clazz.getMethod("getYear"));
// 输出 public int java.util.Date.getYear()
System.out.println(clazz.getMethod("getYear").invoke(clazz.newInstance(),null));
// 因为getYear()不需要参数,所以这里传参为null
// 输出120 (是2020-1900的结果)
当通过Method
的invoke()
方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序需要调用某个private
方法,可以先调用setAccessible()
方法:
setAccessible(boolean flag)
:将Method
对象的accessible
设置为指定的布尔值。值为true
,指示该Mtrhod
在使用时应该取消Java语言的访问权限检测;值为false
,则需要权限检查
上面还提到过getFields()
或getField()
来访问成员变量。Field
提供了下面2组方法来读取或设置成员变量值:
getXxx(Object obj)
:获取obj
对象的该成员变量值。此处的Xxx
对应8种基本类型,如果该成员变量的类型是引用类型,则取消get
后面的Xxx
。例如someField.getInt(new Aclass());
setXxx(Object obj, Xxx val)
:将obj
对象的该成员变量设置为val
值。此处的Xxx
对应8种基本类型,如果该成员变量的类型是引用类型,则取消get
后面的Xxx
。例如someField.setInt(new Aclass(),1);
一个Demo:
xxxxxxxxxx
import java.lang.reflect.Field;
public class test{
public static void main(String[] args) throws Exception{
Ccc c = new Ccc();
Field field = c.getClass().getDeclaredField("i");
field.setAccessible(true); //设置可以访问
System.out.println(field.getInt(c)); //getXxx(obj)
// c.getClass().getDeclaredField("i").setAccessible(true);
// System.out.println(c.getClass().getDeclaredField("i").getInt(c));
// 注释部分还是会报错 Class test can not access a member of class test$Ccc with modifiers "private"
}
static class Ccc{
private int i = 111;
}
}