Java基础——反射

本文深入探讨了Java反射机制,解释了什么是反射、为何使用反射,详细介绍了反射的原理、主要类,包括Class、Method、Field和Constructor。通过反射,可以在运行时获取类的结构信息,创建对象并访问其成员。此外,还讲解了类加载的三个阶段:加载、连接(验证、准备、解析)和初始化。最后,提供了通过反射进行对象操作的示例。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:


一、反射概述

1、什么是反射?

Java反射机制是在运行状态中,对于任何一个类,都可以知道它所有的成员变量和方法,对于任何一个对象,都可以调用它的任意的属性和方法。这种动态获取信息和动态调用对象的方法的功能称为反射机制。

2、为什么要使用反射?
  • 反射机制是Java框架的灵魂,它可以通过外部文件配置,在不修改源码的情况下能够控制程序,符合设计模式中的开闭原则。

二、深入理解反射机制

1、反射机制原理图

  1. Java程序在计算机中有三个阶段:源码阶段 / 编译阶段、类加载阶段、运行阶段
  2. 在编译阶段时,会将源代码经过javac编译成.class字节码文件
  3. 类加载阶段时,将.class字节码文件通过类加载器 创建一个类对象,并把它放在堆中。此时类中的成员变量和方法都会被当作对象来存储。
  4. 类加载完之后,就会生成一个对象,(在堆中),该对象知道它是属于哪个类的,此时我们就得到了这个对象。就能够操作它的属性和方法。

例如:在运行阶段时,new 了一个Cat对象,在new对象的时候会有一个类加载阶段,这个时候会通过类加载器把字节码文件加载到内存中的堆里去,同时在方法区会生成该对象的字节码的二进制数据。会生成一个对应的Class类对象(有成员信息),此时底层会将成员变量、构造器、方法等都看做成对象来对待。加载完成后就会生成一个Cat对象,该对象知道它是属于Cat类的。

在这里插入图片描述

2、反射机制能做什么?

  1. 在运行时判断任意一个对象所属的类。
  2. 在运行时构造任意一个类的对象。
  3. 在运行时得到任意一个对象的方法和属性。
  4. 生成动态代理

3、反射相关的主要类

  • java.lang.Class : 代表一个类,Class类表示在某个类加载后在堆中生成的对象。
  • java.lang.reflect.Method : 代表类的方法,Method对象表示某个类的方法。
  • java.lang.reflect.Field : 代表类的成员变量,Field对象表示某个类的字段。
  • java.lang.reflect.Constructor : 代表类的构造器,Constructor对象表示某个类的构造器。

注意:发生java.lang.InstantiationException的异常时,检查下列几点:

  1. 看要实例化的对象是否是一个接口或者抽象类等不可被实例化的类。通常将构造方法改为public就好。
  2. 持久化引起的异常,看要实例化的对象的类中是否含有一个无参构造器,如果没有,则添加一个无参构造器。(在Hibernate中就有明确的要求:每一个持久化类都必须带一个不带参数的构造方法。)

使用代码:
1、先创建一个Cat类

public class Cat {
    public String name;
    public int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {
    }

    public void hi() {
        System.out.println("喵喵叫...");
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2、创建Test类测试
获取Cat类对应的Class对象,并得到如下信息

        Class cls = Class.forName("practice.reflection_.Cat");
        //2、输出该对象属于哪个类practice.reflection_.practice.reflection_.Cat
        System.out.println(cls);
        //3、输出的类型是它的运行类型java.lang.Class
        System.out.println(cls.getClass());
        //4、得到包名practice.reflection_
        System.out.println(cls.getPackage().getName());
        //5、得到全类名practice.reflection_.practice.reflection_.Cat
        System.out.println(cls.getName());

通过cls创建对象实例,可以通过API得到对应的字段、构造器、成员方法

/**
       * 6、通过cls创建对象实例
       * 在newInstance的时候,一定要注意该Cat的构造方法是否含有一个无参构造器
       * 若没有无参构造器则会报错。
       */
      Object cat = cls.newInstance();
      //7.1、得到public类型的字段并输出
      Field name = cls.getField("name");//这里name属性是public的,所以没报错
      System.out.println(name.get(cat));

      //7.2、给属性赋值
      name.set(cat,"小花");
      System.out.println(name.get(cat));
      //7.3得到所有的字段属性(public类型)
      Field[] fields = cls.getFields();
      for (Field field : fields) {
          System.out.println(field.getName());
      }
        //8、得到想要的方法并执行。
        Method hi = cls.getMethod("hi");
        hi.invoke(cat);

        //9、得到所需的构造器
        Constructor constructor1 = cls.getConstructor();//()可以传入指定构造参数类型
        System.out.println(constructor1);
        System.out.println(cls.getConstructor(String.class));//得到构造器为一个String类型的构造器对象并输出

        Constructor[] constructors = cls.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName());

        }

4、Class类分析

Ⅰ.基本原理

注意事项:

  • Class类也是类,因此也继承Object类。
  • Class不是new 出来的,而是系统创建的。
  • 对于某个类的Class类对象,内存中只有一份,因此类只加一次。
  • 每个类的对象都记得它是由哪个Class实例来生成。
  • 通过Class类对象可以完整的知道一个类的完整结构,通过一系列的API。
  • 类对象是存储在堆中的。
  • 类的字节码二进制数据,是存放在方法区的。(也称为元数据)
  1. 在new 一个对象的时候,会进入到类加载器中(ClassLoader类),然后调用loadClass方法。
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
  1. 利用反射来创建一个对象的时候,最终还是会进入到loadClass方法。
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
Ⅱ.获取Class类的几种方式
  1. 在编译阶段获取:Class.forName
  2. 在类加载阶段获取:类.Class
  3. 在运行阶段获取:对象.getClass()
  4. 通过类加载器获取类对象
    代码演示
  • 方式一:已知一个类的类名,并且该类在类路径下,可通过Class类的静态方法forName()获取。
    应用场景:多用于配置文件,读取类的全路径,加载类
    public static void main(String[] args) throws ClassNotFoundException {
        /**
         * 方式一:已知一个类的类名,并且该类在类路径下,可通过Class类的静态方法forName()获取。
         * 应用场景:多用于配置文件,读取类的全路径,加载类
         */
        String classAllPath = "practice.reflection_.Cat";//通过读取配置文件获得
        Class cls1 = Class.forName(classAllPath);
        System.out.println(cls1);
  • 方式二:若已知具体的类,可通过类.class来得到,此方法最为安全可靠
    * 应用场景:多用于参数传递;
        /**
         * 方式二:若已知具体的类,可通过类.class来得到,此方法最为安全可靠
         * 应用场景:多用于参数传递;
         */
        Class cls2 = Cat.class;
        System.out.println(cls2);
  • 方式三:若已知对象实例,可通过对象实例.getClass()得到
    * 应用场景:有对象实例,可以得到它的运行类型。
        /**
         * 方式三:若已知对象实例,可通过对象实例.getClass()得到
         * 应用场景:有对象实例,可以得到它的运行类型。
         */
        Cat cat = new Cat();
        Class cls3 = cat.getClass();
        System.out.println(cls3);
  • 方式四:通过类加载器获取
    * 应用场景:在加载阶段
        /**
         * 方式四:通过类加载器获取
         * 应用场景:在加载阶段
         */
        //先得到类加载器classLoader
        ClassLoader classLoader = cat.getClass().getClassLoader();
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);
  • 其它获取方式
        /**
         * 其它方式:
         * 基本数据类型int.class
         * 包装类Integer.TYPE
         * 都可以得到对应的Class
         */
        Class<Integer> integerClass = int.class;
        System.out.println(integerClass);
        Class<Integer> type = Integer.TYPE;
        System.out.println(type);
        //int 和Interger在底层是同一个对象。
        System.out.println(type.hashCode()==integerClass.hashCode());

    }

Ⅲ.哪些类有Class对象
  1. 外部类、四大内部类、接口
  2. 枚举、注解
  3. 数组、基本数据类型、void

5、类加载

Ⅰ.基本说明
  1. 静态加载:编译时就加载相关的类,如果没有则会报错。
  2. 动态加载:只有执行到该代码时才会加载相关的类,如果没有才报错;运行时没有执行到该代码即使不存在相关的类也不会报错。
  • 类加载时机
  1. 当创建对象时new 一个对象(静态加载)
  2. 当子类被加载时,父类也被加载(静态加载)
  3. 调用类中的静态成员时(静态加载)
  4. 通过反射(动态加载)
Ⅱ.类加载的三个阶段

1、加载 ——>2、连接(验证—>准备—>解析)——>3、初始化
在这里插入图片描述
在这里插入图片描述

1、加载

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

2.1连接阶段——验证

  • 目的是确保当前Class文件的字节流中的信息符合JVM的要求,并且不会危害到JVM自身的安全
  • 包括文件格式验证(是否以魔数Ox cafe babe,)、字节码验证、元数据验证和符号引用验证。
  • 可以使用 -Xverify : none 参数来关闭大部分的类验证措施,可以缩短虚拟机类加载的时间。

2.2连接阶段——准备

  • 该阶段JVM对静态变量分配内存并进行默认初始化(根据数据类型来确定默认初始值);这些变量所使用的内存都将在方法区中分配。(不是静态变量不会分配内存)
    分配方法如下:
public class A {
	//n1不是静态变量,不会分配内存
    public int n1 = 20;
    //n2是静态变量,会分配内存,初始值为0
    public static int n2 = 18;
    //static final n3为常量,一旦赋值便不可改变,直接分配n3 = 30
    public static final int n3 = 30;
    
}

2.3连接阶段——解析

  • 虚拟机将常量池内的符号引用替换为直接引用的过程。
    1. 符号引用:

3、初始化阶段

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

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

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


三、通过反射的常用操作

1、获取类的结构信息

第一组:java.lang.Class类

Car类:

@Deprecated//注解
class Car implements Usb{
    private int price;
    public String brand;

    public Car(int price, String brand) {//有参(2个)构造器
        this.price = price;
        this.brand = brand;
    }

    public Car(int price) {//一个有参构造器
        this.price = price;
    }

    public Car() {//无参构造
    }



    public  void doWork(){
        System.out.println("执行car的公共doWork方法");
    }
    private void stopWork(){
        System.out.println("执行car的私有stopWork方法");
    }


    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "price=" + price +
                ", brand='" + brand + '\'' +
                '}';
    }
}

interface Usb{
}

测试java.lang.Class类的常用API

public class RelectUtils{
    public static void main(String[] args) throws ClassNotFoundException {
        String fileAllPath = "practice.reflec_01.Car";
        Class cls = Class.forName(fileAllPath);//得到Class类对象
        //1、getName:获取全类名
        System.out.println("1==============");
        System.out.println(cls.getName());
        System.out.println("2--------------");
        //2、getSimpleName:获取简单类名
        System.out.println(cls.getSimpleName());
        System.out.println("3--------------");
        //3、getFiles:获取所有public修饰的属性,包含本类以及父类的
        Field[] fields = cls.getFields();
        for (Field field : fields) {//应该获取brand属性,因为它是public的
            System.out.println(field.getName());
        }
        System.out.println("4--------------");
        //4、getDeclaredFields:获取本类中所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField.getName());
        }
        System.out.println("5--------------");
        // 5、getMethods:获取所有public方法,包含本类及父类的
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        System.out.println("6------------------");
        // 6、getDeclaredMethods:获取所有方法(不包含父类)
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.getName());
        }
        System.out.println("7-------------------");
        //7、getConstrctors:获取本类所有Public修饰的构造器
        Constructor[] constructors = cls.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName());
        }
        System.out.println("8---------------------");

        //8、getDeclaredConstrctors:获取本类所有的构造器
        Constructor[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getName());
        }
        System.out.println("9---------------------");
        //9、getPackage:以Package形式返回包信息
        Package aPackage = cls.getPackage();
        System.out.println(aPackage.getName());
        System.out.println("10---------------------");
        //10、getSuperclass:以class形式返回父类信息
        Class superclass = cls.getSuperclass();
        System.out.println(superclass.getName());
        System.out.println("11-------------------");
        //11、以Class[]形式返回接口信息。
        Class[] interfaces = cls.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println(anInterface.getName());
        }
        System.out.println("12----------------");
        Annotation[] annotations = cls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解"+annotation);
        }

    }

}

第二组:java.lang.reflect.Field类

演示代码:

        System.out.println("=========第二组==========");
        Field[] declaredFields1 = cls.getDeclaredFields();
        System.out.println("1、--------------");
        //1、getModifiers以int形式返回修饰符
        //默认修饰符为0,public是1,private是2,protected是4
        //static是8,final是16,public static = 1 + 8 = 9
        for (Field field : declaredFields1) {
            System.out.println(field.getModifiers());
        }

        System.out.println("2、--------------------");
        //2、getType:以Class形式返回类型
        for (Field field : declaredFields1) {
            System.out.println(field.getType());
        }

        System.out.println("3、-------------------");
        //3、getName:返回属性名
        for (Field field : declaredFields1) {
            System.out.println(field.getName());
        }

第三组:java.lang.reflect.Method类

代码演示:

System.out.println("=========第三组==========");
        System.out.println("1、--------------");
        /**
         * 1、getModifiers:以int形式返回修饰符
         * 2、getName:返回方法名
         * 3、getReturnType:以class形式获取返回类型
         * 4、getParameterTypes:以class[]返回形式参数数组
         */
        Method[] methods1 = cls.getMethods();
        for (Method method : methods1) {
            System.out.println("返回修饰符类型(int)"+method.getModifiers()
            +"\t方法名:"+method.getName()+"\t以Class形式获取返回类型"+method.getReturnType());
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该方法的形参类型"+parameterType);
            }
        }

第四组:java.lang.reflect.Constructor类

        System.out.println("=========第四组==========");
        Constructor[] constructors1 = cls.getConstructors();
        for (Constructor constructor : constructors1) {
            System.out.println("返回构造器名:(全类名)"+constructor.getName()+
                    "\t以int类型返回修饰符:"+ constructor.getModifiers());
            Class[] parameterTypes = constructor.getParameterTypes();//以class类型返回形式参数数组
            for (Class parameterType : parameterTypes) {
                System.out.println("返回参数类型:"+parameterType);
            }
        }

2、通过反射创建对象

  • 两种方式
  1. 方式一:调用类中的public修饰的无参构造器
  2. 方式二:调用类中指定的构造器
  • class类相关方法
  1. newInstance : 调用类中的无参构造器,获取对应类的对象。
  2. getConstructor : (Class …clazz ),根据参数列表,获取对应的public构造器对象。
  3. getDecalaredConstructor : (Class …clazz),获取所有对应的构造器对象。
  • constructor相关方法
  1. setAccessible : 暴破
  2. newInstance (Object …object ):调用构造器

代码演示:

测试
1、通过public无参构造来创建对象实例

		String fileAllPath = "practice.reflec_01.Car";
        Class cls = Class.forName(fileAllPath);//得到Class类对象
		//1、通过public无参构造来创建对象实例
        Object car1 = cls.newInstance();//1.1直接调用Instance方法得到一个对象
        System.out.println(car1);

2、通过Public的有参构造器创建对象实例

        //2、通过Public的有参构造器创建对象实例
        //2.1得到class对象的构造器,并传入构造器参数对应的class类型
        Constructor constructor = cls.getConstructor(int.class);
        //2.2通过newInstance创建该构造器对应的对象,并传入自己指定的值
        Object car2 = constructor.newInstance(2880000);
        System.out.println(car2);

3、通过非public的有参构造器来创建对象实例

        //3、通过非public的有参构造器来创建对象实例
        //3.1创建一个可以得到全部修饰类型的构造器,并指定构造器的参数对应的Class类型
        Constructor declaredConstructor = cls.getDeclaredConstructor(String.class);
        //3.2暴力破解的方式得到非Public类型的构造器对象。使用反射可以得到私有属性
        declaredConstructor.setAccessible(true);
        //3.3创建需要的对象,并传入需要设置或修改的值。
        Object car3 = declaredConstructor.newInstance("保时捷911");
        System.out.println(car3);

    }

演示类:Car的基本信息设置

class Car implements Usb {
    private int price = 20;
    public String brand = "宝马7";

    public Car(int price, String brand) {//有参(2个)构造器
        this.price = price;
        this.brand = brand;
    }

    public Car(int price) {//一个有参构造器
        this.price = price;
    }

    public Car() {//无参构造
    }
    private Car(String brand){
        this.brand = brand;
    }


    public void doWork() {
        System.out.println("执行car的公共doWork方法");
    }

    private void stopWork() {
        System.out.println("执行car的私有stopWork方法");
    }


    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "price=" + price +
                ", brand='" + brand + '\'' +
                '}';
    }
}

interface Usb {
}

3、通过反射访问类中成员

访问属性

代码演示:

        //通过反射来操作对象的成员变量
        Object car = cls.newInstance();//得到一个对象
        //访问私有属性需要暴破并且getDeclaredField 而不是getField
        Field price = cls.getDeclaredField("price");//设置想要的设置属性,传入属性名称
        price.setAccessible(true);//通过反射的暴力破解(取消安全检查),允许访问私有属性
        price.set(car,9900000);//设置属性值
        price.set(null,80000000);//Object参数也可以是null,因为它是static修饰的,static属于所有类。
        //不是静态的不能置空!!!!
        System.out.println(car);

操作方法

代码演示:

        //通过反射来操作对象的成员方法
        Object car5 = cls.newInstance();//1、先得到对象实例
        //2、通过对象实例获取所有可操作的方法,并传入要获取的方法名,以及形参列表的Class类型
        Method doWork = cls.getDeclaredMethod("doWork", int.class);
        doWork.setAccessible(true);//想要通过反射访问私有的方法需要暴破
        doWork.invoke(car5,0);//4、调用invoke方法,传入实例对象,以及对应的实参。
        //如果是静态方法,car5可以替换为Null,否则必须写对应的对象实例

总结

  • 反射的重点在于掌握类加载的过程(三个阶段),分别是加载、连接(验证、准备、解析)、初始化。然后就是创建对象实例的三种方式(无参构造newInstance,public有参构造,非public有参构造),以及常用类的方法的使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值