反射相关
Class类分析
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类的完整结构
- Class对象是放在堆内存中的
- 类的字节码二进制数据,是放在方法区的,有的地方也称为类的元数据
Class类常用方法
String classpath = "com.xxx.Person";
// 获取Person类对应的Class对象
Class<?> clazz = Class.forName(classpath);
// 获取运行类型 直接输出clazz则表示为哪个类的Class对象
clazz.getClass();
// 获取包名
clazz.getPackage().getName();
// 获取全类名
clazz.getName();
// 通过clazz创建对象实例
Person p = (Person)clazz.newInstance();
// 获取(非私有属性)属性 例如pName
Field pName = clazz.getField("pName");
// 给属性设置值
pName.set(p, "设置了新值");
// 获取所有属性
Field[] fields = clazz.getFields();
for(Field field : fields) {
// do something...
}
获取Class类对象的几种方式
-
前提:已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法**forName()**获取。
例如:Class clazz = Class.forName(“java.lang.Person”);
可能出现的异常:ClassNotFoundException
应用场景:多用于配置文件读取类全路径,加载类
-
前提:已知具体的类,通过类的class获取,该方式最为可靠程序性能最高。
例如:Class clazz = Person.class; System.out.println(String.class);
应用场景:多用于参数传递,比如通过反射得到对应构造方法对象
-
前提:已知某个类的实例,调用该实例的**getClass()**方法获取Class对象。
实例:Class clazz = 对象.getClass(); //运行类型
应用场景:通过创建好的对象,获取Class对象。
-
其它方式:通过类加载器获取类的Class对象
ClassLoader cl = 对象.getClass().getClassLoader();
Class clazz = cl.loadClass(“全类名”);
注:上面4个clazz 其实是同一对象(符合上述
Class类分析
第三条) -
八种基本数据类型 按照如下方式得到Class类对象
Class<Integer> intClazz = int.class; //int Class<Character> charClazz = char.class; Class<Boolean> boolClazz = boolean.class; ......
-
八种基本数据类型对应的包装器类,可以通过.TYPE 得到Class类对象
Class<Integer> intType = Integer.TYPE; //int ......
注 intClazz和intType也是同一对象 它们的hashCode是相等的
哪些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类 例如:String.class
- interface接口 例如:Serializable.class
- 数组 例如:Integer[].class / Double[][].class
- enum枚举 例如:Thread.State.class
- annotation注解 例如:Deprecated.class
- 基本数据类型 例如:long.class / Integer.class
- void 例如:void.class
- Class 例如:Class.class (其实属于第一种 外部类)
类加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性强。
- 动态加载:运行时加载需要的类,如果运行时没使用到该类则不报错,降低依赖性。
类加载时机
-
创建对象时(new)
–静态加载
-
当子类被加载时,父类也加在
–静态加载
-
调用类中的静态成员
–静态加载
-
通过反射
–动态加载
类加载的三个过程
图片截图于B站 韩顺平 的视频
(1)加载阶段
JVM虚拟机在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
(2)连接阶段
验证阶段
- 目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
-
包括:文件格式验证(是否以
魔数
0xcafebabe开头)、元数据验证、字节码验证和符号引用验证(举例说明)。 -
可以考虑使用
-Xverify:none
参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
准备阶段
-
JVM虚拟机会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始化值如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配。
-
举例
// n1是实例属性,不是静态变量,在准备阶段,不会分配内存
public int n1 = 10;
// n2是静态变量,分配内存且在准备阶段默认初始化为0,不是20 注意:20是在初始化阶段分配的值
public static int n2 = 20;
// n3是静态变量且final修饰的常量,与一般的静态变量不同,final修饰的一旦赋值就不变了。因此n3此阶段直接为30
public static final int n3 = 30;
解析阶段
- JVM虚拟机将常量池内的***符号引用***替换为***直接引用***的过程。
(3)初始化阶段
- 到初始化阶段,才是真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程。
-
()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有
静态变量
的赋值动作和
静态代码块
中的语句,并进行合并。 - JVM虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
// 正是因为这个机制,才能保证某个类在内存中,只有一份Class对象
// 验证了 【Class类分析】 中的第3条
synchronized(getClassLoadingLock(name)) {......}
- 初始化例子
/*
执行结果:
static代码块执行
-1
num在连接阶段会被初始化为0,在初始化阶段clinit对两个赋值(num=1和num=-1合并)所以num=-1,参照初始化阶段第2条描述。
*/
class Test {
static {
System.out.println("static代码块执行");
num = 1;
}
static int num = -1;
public static void main(String[] args) {
System.out.println(Test.num);
}
}
通过反射创建对象
// 1.获取xxx类的Class对象
Class<?> clazz = Class.forName("类全路径名") // 例com.abc.User
// 2.通过public的无参构造方法创建实例
Object o = newInstance();
// 3.通过public的有参构造方法创建实例
// getConstructor()参数应该传入构造器对应的参数类型 例如String.class,可以多个参数。
// newInstance()传入getConstructor()参数对应的类型 例如String.class对应String类型,可以多个参数。
Constructor<?> constructor = clazz.getConstructor(xxx.class);
Object obj = constructor.newInstance(参数);
// 4.非public的有参构造方法创建实例
// getDeclaredConstructor()参数应该传入构造器对应的参数类型 例如int.class,可以多个参数。
// newInstance()传入getConstructor()参数对应的类型 例如int.class对应int类型,可以多个参数。
Constructor<?> constructor1 = clazz.getDeclaredConstructor(xxx.class);
// setAccessible爆破,使用反射可以访问非public构造方法
// 如果不设置setAccessible(true) Object obj1 = constructor1.newInstance(参数)将会报IllegalAccessException
constructor1.setAccessible(true);
Object obj1 = constructor1.newInstance(参数);
通过反射访问类中成员(属性)
// 1.获取xxx类的Class对象
Class<?> clazz = Class.forName("类全路径名") // 例com.abc.User
// 2.创建对象
Object obj = clazz.newInstance(); // obj运行类型是User
// 3.通过反射得到public的 属性对象
Field field = clazz.getField("属性名") // 例 age (public int age)
field.set(obj,99); //通过反射操作属性 此时将age赋值为99 如果输出obj的话,age=99 如有其他属性则其他属性为null
System.out.println(obj); // User[age=99, name=null]
System.out.println(field.get(obj)); // 99
// 4.通过反射操作 非public的static的 属性对象
Field field1 = clazz.getField("属性名") // 例 name (private static String name)
// setAccessible爆破,使用反射可以访问非public属性
// 如果不设置setAccessible(true) field1.set(obj,"你的名字")将会报IllegalAccessException
field1.setAccessible(true);
field1.set(obj,"你的名字");
// 因为name是static的,obj也可以写成null
//field1.set(null,"你的新名字");
System.out.println(obj); // User[age=null, name="你的名字"]
System.out.println(field1.get(obj)); // 你的名字
// static还可以这样写
//System.out.println(field1.get(null));
通过反射访问类中的方法
// 1.获取xxx类的Class对象
Class<?> clazz = Class.forName("类全路径名") // 例com.abc.User
// 2.创建对象
Object obj = clazz.newInstance(); // obj运行类型是User
// 3.调用public的方法
// 得到func方法对象
// 对于public方法,下面两种都可正常拿到
Method method = clazz.getMethod("方法名"); //例func 如果有参还需要在方法名后指定 例 getMethod("func",String.class);
Method method1 = clazz.getDeclareMethod("方法名"); //例func
// 调用
method.invoke(obj, "func方法调用了...");
// 4.调用非public的方法 private static String func1(int age, String msg);
Method method2 = clazz.getDeclareMethod("方法名", int.class, String.class); //例func1
// 爆破,原理同前面的
method2.setAccessible(true);
method2.invoke(obj, 123, "func1方法调用了");
// 由于func1方法是静态的,也可以这样调用
method2.invoke(null, 123, "func1方法又调用了");
// 5.反射中的方法返回值无论该方法原来的返回值是什么都统一返回Object,但是它的运行类型和方法的返回类型一致
System.out.println(method2.invoke(null, 123, "func1方法又调用了").getClass()); // String