Java反射

本文详细介绍了Java反射机制,包括反射的概念、主要类如Class、Method、Field和Constructor的使用,以及反射的优缺点。还探讨了类加载的过程、双亲委派机制以及ClassLoader的作用。通过实例展示了如何通过反射获取类的结构信息、创建对象以及进行反射爆破操作。反射在框架设计和动态加载类时发挥关键作用,但也存在性能影响,可以通过设置访问检查来优化。
摘要由CSDN通过智能技术生成

目录

反射(reflection)

反射机制

反射相关的主要类:

反射的优缺点

反射机制的优化 ----- 关闭访问检查

双亲委派机制

2.3 CLassLoader类

Class 类

Class类的常用方法

获取Class类对象的方式(6种)

类加载

类的加载流程图

类加载的五个阶段

通过反射获取类的结构信息

通过反射创建对象

反射爆破操作


反射(reflection)

反射 是在不修改源码情况下 来控制程序 符合设计模式的ocp原则(开闭原则 :不修改 源码 扩容功能)

方法在反射中 也被视为对象 把java 的万物皆对象 在反射中体现出了

反射机制

  1. 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如构造方法 成员变量 成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用的到

  2. 加载完类之后,在堆中就产生了一个class类型的对象,(一个类只有一个class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称为:反射

    java程序在计算机有三个阶段

 

计算机有三个阶段 代码阶段/编译阶段 class类阶段 运行阶段

由类加载器,来完成class类的生成

反射相关的类 在API java .lang.refection 中

反射相关的主要类:

  1. java.lang.Class 代表一个类 Class 对象表示某个类加载后在堆中的对象

  2. java.lang.reflect.Method 代表类的方法 Method对象表示某个类的方法

  3. java.lang.reflect.Field :代表类的成员方法 Field对象代表某个类的成员变量

  4. java.lang.reflect..Constructor 代表类的构造方法 Constructor 对象代表某个类的构造器

反射的优缺点

优点 可以动态的创建和使用对象(也是框架的底层核心) 使用灵活没有反射机制 框架机制就失去底层支撑

缺点 使用反射基本就是解释执行 对执行速度有影响

反射机制的优化 ----- 关闭访问检查

  1. Method Field 和 Constructor 对象都有setAccessible() 方法 Accessible 代表 无障碍 可使用的

  2. setAccessible 作用是启动和禁用访问安全检查的开关

  3. 参数值为true 表示 反射的对象在使用是取消访问检查 提高反射效率 参数值为false 则表示反射的对象执行访问检查

双亲委派机制

  1. 双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.

  2. 双亲委派模型工作过程:

    1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求 委派给父类加载器Extension ClassLoader去完成.

    2)当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派 给父类加载器Bootstrap ClassLoader去完成.

    3)如果Bootstrap ClassLoader加载失败,就会让Extension ClassLoader尝试加载.

    4)如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载.

    5)如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.

    6)如果均加载失败,就会抛出ClassNotFoundException异常.

3.例子:

  当一个Hello.class这样的文件要被加载时.不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法.父类中同理会先检查自己是否已经加载过,如果没有再往上.注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.

2.3 CLassLoader类

  1. ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的代模块称之为“类加载器”.把【.class】文件加载到jvm虚拟机当中,转换为一个Class的对象【类的字节码对象 类的类对象】

     2.ClassLoader的方法:

        static ClassLoader getSystemClassLoader()

        返回用于委派的系统类加载器

        ClassLoader getParent()

        返回父类加载器进行委派

Class 类

  1. Class 也是类 因此也继承 Object类

  2. Class类对象不是new出来的 而是系统创建的

  3. 对于某个类的Class类对象 在内存中只有一份,因此类只加载一次

  4. 每个类的实例都会记得自己是由哪个Class实例所生成

  5. 通过Class可以完整地得到一个类的完整结构 通过一系列API

  6. Class对是存放在堆中

  7. 类的字节码二进制数据,是放在方法区中,有的地方称为类的元数据(包括 方法代码 变量名 方法名 访问权限 等等)

Class类的常用方法

//演示 Class 类的常用方法
        String classAllPath = "反射.Class_.Car";
//1.获取到Car 类 对应的Class对象   Class.forName(classAllPath)
 //<?> 表示不确定的java类型
        Class<?>cls = Class.forName(classAllPath);
​
//2 输出cls   显示cls对象,
        System.out.println(cls);//是哪个类的Class对象  class 反射.Class_.Car
 // 3 输出cls运行类型   cls.getClass()
    System.out.println(cls.getClass());//class java.lang.Class
​
// 4 得到包名  cls.getPackage().getName()
        System.out.println(cls.getPackage().getName());//反射.Class_
​
//5  得到全类名  cls.getName()
        System.out.println(cls.getName());//反射.Class_.Car
​
// 6 通过cls创建 对象实例  newInstance()
    Car car =  (Car)cls.newInstance();
   System.out.println(car);//car.toString  Car{brand='宝马', price=500000, color='白色'}
​
// 7 通过反射获取属性  cls.getField("属性名");
        Field brand = cls.getField("brand");
//brand.get(car)  获取 属性
        System.out.println(brand.get(car));//宝马
 // 8  通过反射给属性赋值  
        brand.set(car,"奔驰");
        System.out.println(brand.get(car));//奔驰
// 9 得到所有的属性
        System.out.println("所有的属性信息-============");
        Field[] fields = cls.getFields();
        for (Field field : fields) {
            System.out.println(field.get(car));
        }
    }

获取Class类对象的方式(6种)

第一种

前提:已知一个全类名 且该类在类路径下 可通过Class类的静态方法 forNam("路径") 获取

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

// 方式一  Class.forName 
        String classAllPath = "反射.Class_.Car";
        Class<?> aClass1 = Class.forName(classAllPath);
        System.out.println(aClass1);
        //class 反射.Class_.Car

第二种

前提:若已知具体的类,通过类的class获取,该方法最为安全可靠程序性能最高

应用场景:多用于参数传递 比如通过反射得到对应的构造器对象

// 方式二  类名.class, 应用场景 :应用参数传递
        Class<Car> aClass2 = Car.class;
        System.out.println(Car.class);
        //class 反射.Class_.Car

第三种

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

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

//第三种 通过对象的实例 获取Class对象
        Car car = new Car();
        Class<? extends Car> aClass3 = car.getClass();
        System.out.println(aClass3);
        //class 反射.Class_.Car

第四种

通过类加载器 来获取到Class对象 拓展知识【类加载器有4种】

//1)先得到类加载 car  
    ClassLoader classLoader = car.getClass().getClassLoader();
// 通过类加载得到 Class对象
        Class<?> aclass4 = classLoader.loadClass(classAllPath);
        System.out.println(aclass4);
        //class 反射.Class_.Car   

第五种

基本数据类型(int,char,boolean,double,yte,short) 按如下方式得到Class类对象

//第五种 基本数据类型(int,char,boolean,double,byte,short) 按如下方式得到Class类对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);//int

第六种

基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象

第六种
基本数据类型对应的包装类,可以通过    **.TYPE **     得到Class类对象
    //第六种 基本数据类型的包装类  可以通过  .TYPE 得到Class类对象
            Class<Integer> type1 = Integer.TYPE;
            Class<Character> type2 = Character.TYPE;
            System.out.println(type1);//int
// 哈希值都一样  表示都是同一个
  System.out.println(integerClass.hashCode());//1580066828
  System.out.println(type1.hashCode());//1580066828

哪些类型有Class 对象

1、外部类,成员内部类,静态内部类,局部内部类,匿名内部类

2、interface:接口

3、数组

4、enum :枚举

5annotation、注解

6、基本数据类型

7、void

//  演示那些类型有Class对象
            Class<String> cls1 = String.class;//外部类
            System.out.println(cls1);//  class java.lang.String
​
            Class<Serializable> cls2 = Serializable.class;//接口
            System.out.println(cls2);//interface java.io.Serializable
​
            Class<Integer[]> cls3 = Integer[].class;//数组
            System.out.println(cls3);//class [Ljava.lang.Integer;
​
            Class<Integer[][]> cls4 = Integer[][].class;//二维数组
            System.out.println(cls4);//class [[Ljava.lang.Integer;
​
            Class<Deprecated> cls5 = Deprecated.class;//注解
            System.out.println(cls5);  // interface java.lang.Deprecated
​
            Class<Thread.State> cls6 = Thread.State.class;//枚举
            System.out.println(cls6); //class java.lang.Thread$State
​
            Class<Long> cls7 = long.class;//基本数据类型
            System.out.println(cls7);   // long
​
            Class<Void> cls8 = void.class;//void数据类型
            System.out.println(cls8);  //  void
​
            Class<Class> cls9 = Class.class;//Class类
            System.out.println(cls9); //class java.lang.Class

类加载

反射机制是java实现动态语言的 关键,也就是通过反射实现类动态加载

1、静态加载 :编译时加载相关的类,如果没有则报错,依赖性太强

2、动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

类的加载时机

1、当创建对象时 (new)// 静态加载

2、当子类被加载时 // 父类也加载 //静态加载

3、调用类中的静态方法// 静态加载

4、通过反射 // 动态加载

类的加载流程图

 

 

类加载的五个阶段

  1. 加载阶段

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

  2. 2.1)连接阶段 -验证

    • 目的是为了确保Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

    • 包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证、字节码验证、符号引用验证

    • 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟墙类加载的时间

    2.2)连接阶段 - 准备

    JVM会在该阶段对静态变量,分配内存 并默认初始化(对应数据类型的默认初始值 ,如:0、0L、null、false等)。这些变量所使用的内存都健在方法区中进行分配

    2.3)连接阶段 - 解析

    虚拟机将常量池的符号引用替换为直接引用的过程

  3. lnitalization (初始化)

    • 到初始化阶段,才真正开始执行类中定义分java程序代码,此阶段是执行<clinit>()方法的过程

    • <clinit>()方法是由编译器按语句在源文件出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块的语句,并进行合并

    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕

通过反射获取类的结构信息

第一组

//得到Class对象
Class<?> cls = Class.forName("反射.类加载.Person");
​
//cls.getName()  获取全类名
System.out.println(cls.getName());
​
//cls.getSimpleName() 获取简单类名
System.out.println(cls.getSimpleName());
​
//cls.getFields()  获取所有public 修饰的属性 ,包含本类及父类的
Field[] fields = cls.getFields();
​
//cls.getDeclaredFields() 获取[本类]中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
​
//cls.getMethods() 获取所有public 修饰的方法 包含本类及父类的
Method[] methods = cls.getMethods();
​
//cls.getDeclaredMethods() 获取本类的所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
​
//cls.getConstructors() 获取所有public 修饰的构造器 包含本类
Constructor<?>[] constructors = cls.getConstructors();
​
// cls.getDeclaredConstructors() 获取所有的本类构造器
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
​
//cls.getPackage() 以Package 的形式返回包信息
System.out.println(cls.getPackage());
​
//cls.getSuperclass() 以class形式返回 父类信息
Class<?> superclass = cls.getSuperclass();
​
// cls.getInterfaces() 以Class [] 形式返回接口信息
Class<?>[] interfaces = cls.getInterfaces();
​
//cls.getAnnotatedInterfaces() 以Annotation[]的形式返回注解信息
AnnotatedType[] annotatedInterfaces = cls.getAnnotatedInterfaces();

第二组

java.lang.reflect.Field 类

与属性 相关的方法
getModifiers: 以int 形式返回修饰符
【说明 :默认修饰符 是 0 ,public 是 1, private  是 2 ,protected 是 4 ,static 是8  final 是 16】
getType : 以Class 形式返回类型
    
getName : 返回属性名 
    
            Class<?> name = Class.forName("反射.类加载.Person");
        Field[] fields = name.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("本类中的所有属性"+field.getName()
            +"该属性的修饰符"+field.getModifiers()+"该属性的类型"+field.getType());
        }
/*
本类中的所有属性name该属性的修饰符1该属性的类型class java.lang.String
本类中的所有属性job该属性的修饰符0该属性的类型class java.lang.String
本类中的所有属性age该属性的修饰符4该属性的类型int
本类中的所有属性sal该属性的修饰符2该属性的类型double
*/

第三组

java.lang.refiect.Method 类

与 方法 相关的方法
getModifiers:以int形式返回修饰符
    【说明 :默认修饰符 是 0 ,public 是 1, private  是 2 ,protected 是 4 ,static 是8  
final  是 16 】
    getReturnType : 以Class 形式获取 返回类型
        
    getName :返回方法名
      
    getParameterType :以Class[]返回参数类型数组
        
              Method[] declaredMethods = name.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类的所有方法"+declaredMethod.getName()+"该方法的修饰符="
                            +declaredMethod.getModifiers()+"该方法返回类型"+declaredMethod.getReturnType());
            Class<?>[] types = declaredMethod.getParameterTypes();
            for (Class<?> type : types) {
                System.out.println("该方法的形参类型"+type);
            }
        }
/*
本类的所有方法m1该方法的修饰符=1该方法返回类型void
该方法的形参类型class java.lang.String
该方法的形参类型int
该方法的形参类型double
​
本类的所有方法m2该方法的修饰符=4该方法返回类型class java.lang.String
​
本类的所有方法m3该方法的修饰符=2该方法返回类型void
​
本类的所有方法m4该方法的修饰符=0该方法返回类型void
*/
​

第四组

java.lang.reflect.Constructor 类

与构造器 相关的方法
getModifiers:以int 形式返回修饰符
        【说明 :默认修饰符 是 0 ,public 是 1】
    getName : 返回构造器名(全类名)
    getParameterTypes :以Class[] 返回参数类型数组
    
            for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有的构造器"+declaredConstructor.getName()+"");
​
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该构造器的形参类型"+parameterType);
            }
        }
/*
本类中所有的构造器反射.类加载.Person
该构造器的形参类型class java.lang.String
​
本类中所有的构造器反射.类加载.Person
​
*/

通过反射创建对象

方式一:

调用类中的public 修饰的无参构造器

方式二:

调用类中的指定构造器

方式三 Class类相关的方法:

  • newlnstance :调用类中的无参构造器 获取对应类的对象

  • getConstructor(Class..clazz) :根据参数列表,获取对应的构造器对象

  • getDecalaredConstructor(Class...clazz):调用参数列表 ,获取对应的构造器对象

方式四 Constructor 类相关方法:

  • setAccessible :爆破 爆破反射 打破私有权限

  • newlnstance(Object...obj) :调用构造器

反射爆破操作

  1. 根据属性名获取Field对象

    Field f = clazz对象.getDeclaredField(属性名)

  2. 爆破 :f.setAccessible(true);

  3. 访问

    f.set(o,值)//o表示对象

    sout(f.get(o))//o

  4. 注意 :如果是静态属性 则set和get中的参数o 可以写成 null

访问方法

  1. 根据方法名和参数列表获取Method方法对象 :

    Method m = clazz.getDeclaredMethod(方法名.XX.class)//得到本类所有方法

  2. 获取对象 :Object o = clazz.newlnstance();

  3. 爆破 m.setAccessible(true);

  4. 访问 :Object obj = m.invoke(o,实参列表)

  5. 注意 如果是静态方法 , 则invoke的参数o 可以写成 null

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮皮虾_凡夫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值