程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(2)

Java反射小总结

发布于2021-05-29 22:30     阅读(953)     评论(0)     点赞(5)     收藏(3)


反射相关

Class类分析

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例所生成
  5. 通过Class可以完整地得到一个类的完整结构
  6. Class对象是放在堆内存中的
  7. 类的字节码二进制数据,是放在方法区的,有的地方也称为类的元数据

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类对象的几种方式

  1. 前提:已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法**forName()**获取。

    ​ 例如:Class clazz = Class.forName(“java.lang.Person”);

    ​ 可能出现的异常:ClassNotFoundException

    ​ 应用场景:多用于配置文件读取类全路径,加载类

  2. 前提:已知具体的类,通过类的class获取,该方式最为可靠程序性能最高。

    ​ 例如:Class clazz = Person.class; System.out.println(String.class);

    ​ 应用场景:多用于参数传递,比如通过反射得到对应构造方法对象

  3. 前提:已知某个类的实例,调用该实例的**getClass()**方法获取Class对象。

    ​ 实例:Class clazz = 对象.getClass(); //运行类型

    ​ 应用场景:通过创建好的对象,获取Class对象。

  4. 其它方式:通过类加载器获取类的Class对象

    ClassLoader cl = 对象.getClass().getClassLoader();

    Class clazz = cl.loadClass(“全类名”);

    注:上面4个clazz 其实是同一对象(符合上述 Class类分析 第三条)

  5. 八种基本数据类型 按照如下方式得到Class类对象

    Class<Integer> intClazz = int.class;     //int
    Class<Character> charClazz = char.class;
    Class<Boolean> boolClazz = boolean.class;
    ......
    
  6. 八种基本数据类型对应的包装器类,可以通过.TYPE 得到Class类对象

    Class<Integer> intType = Integer.TYPE;     //int
    ......
    

注 intClazz和intType也是同一对象 它们的hashCode是相等的

哪些类型有Class对象

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类 例如:String.class
  2. interface接口 例如:Serializable.class
  3. 数组 例如:Integer[].class / Double[][].class
  4. enum枚举 例如:Thread.State.class
  5. annotation注解 例如:Deprecated.class
  6. 基本数据类型 例如:long.class / Integer.class
  7. void 例如:void.class
  8. Class 例如:Class.class (其实属于第一种 外部类)

类加载

  • 静态加载:编译时加载相关的类,如果没有则报错,依赖性强。
  • 动态加载:运行时加载需要的类,如果运行时没使用到该类则不报错,降低依赖性。

类加载时机

  • 创建对象时(new) –静态加载
  • 当子类被加载时,父类也加在 –静态加载
  • 调用类中的静态成员 –静态加载
  • 通过反射 –动态加载

类加载的三个过程

在这里插入图片描述

图片截图于B站 韩顺平 的视频

(1)加载阶段

JVM虚拟机在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

(2)连接阶段

验证阶段
  1. 目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  2. 包括:文件格式验证(是否以魔数 0xcafebabe开头)、元数据验证、字节码验证和符号引用验证(举例说明)。
  3. 可以考虑使用 -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)初始化阶段

  1. 到初始化阶段,才是真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程。
  2. ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和 静态代码块中的语句,并进行合并。
  3. 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 


所属网站分类: 技术文章 > 博客

作者:听说你很拽

链接:http://www.javaheidong.com/blog/article/207627/44199e4f80e2682533ab/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

5 0
收藏该文
已收藏

评论内容:(最多支持255个字符)