反射

本文详细介绍了Java反射机制,包括如何获取和使用Class对象,动态创建和调用对象,以及反射在框架中的核心作用。同时,讨论了反射的优缺点,并提供了代码示例展示反射调用的性能优化。此外,还讲解了类加载的时机和过程,以及如何通过反射获取类的结构信息。最后,展示了如何通过反射创建对象和访问类的属性和方法。
摘要由CSDN通过智能技术生成

反射

反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息 并能操作对象的属性一级方法 反射在设计模式和框架底层都会用到

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

image-20210928151015835

反射机制可以完成:

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理
public class ReflectionDemo01 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        String classPath = "com.chenxiii.reflection.Cat";
        String methodName = "hi";

        // 1.加载类 返回Class类型的对象cls
        Class cls = Class.forName(classPath);
        // 2.通过 cls 得到加载的类中的方法
        Object o = cls.newInstance();
        System.out.println(o.getClass());
        // java.lang.reflect.Method: 代表类的方法
        // 在反射中 可以把方法视为对象
        Method method = cls.getMethod(methodName);
        method.invoke(o);

        // java.lang.reflect.Field: 代表类的成员变量 Field对象表示某个类的成员变量
        // 得到name字段
        Field name = cls.getField("name"); // getField只能得到public修饰的属性
        System.out.println(name);

        // java.lang.reflect.Constructor: 代表类的构造方法
        // 得到无参构造器
        Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型
        // 得到带参数的构造器
        Constructor constructor1 = cls.getConstructor(String.class); // 传入String.class
        System.out.println(constructor); //public com.chenxiii.reflection.Cat()
        System.out.println(constructor1); //public com.chenxiii.reflection.Cat(java.lang.String)

    }
}

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

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

public class ReflectionDemo02 {
    public static void main(String[] args) throws Exception {
        m1();
        m2();
    }

    // 传统方法调用hi
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方法: " + (end - start)); // 3
    }

    // 反射机制调用hi
    public static void m2() throws Exception {
        Class cls = Class.forName("com.chenxiii.reflection.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射方法: " + (end - start)); // 24
    }
}

反射调用优化

  1. Method Field Constructor对象都有setAccessible()方法
  2. setAccessible作用时启动和禁用访问安全检查的开关
  3. 参数值为true表示 反射的对象在使用时取消访问检查 提高反射的效率 参数值为false则表示反射的对象执行访问检查
    public static void m3() throws Exception {
        Class cls = Class.forName("com.chenxiii.reflection.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true); // 在反射调用方法时 取消访问检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射优化方法: " + (end - start)); // 16
    }

Class类

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

常用方法:

public class ClassDemo01 {
    public static void main(String[] args) throws Exception {

        String classPath = "com.chenxiii.reflection.Car";
        // 1. 获取到Car类对应的class对象
        // <?> 表示不确定的Java类型
        Class<?> cls = Class.forName(classPath);
        // 2. 输出cls
        System.out.println(cls); //显示cls对象是哪个类的Class对象 class com.chenxiii.reflection.Car
        System.out.println(cls.getClass()); //输出运行类型 class java.lang.Class
        // 3. 得到包名
        System.out.println(cls.getPackage().getName()); // com.chenxiii.reflection
        // 4. 得到全类名
        System.out.println(cls.getName());// com.chenxiii.reflection.Car
        // 5. 通过cls创建对象实例
        Car car = (Car) cls.newInstance();
        // 6. 通过反射获取属性 属性得是public
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car)); // 劳斯莱斯
        // 7. 通过反射给属性赋值
        brand.set(car, "奔驰");
        System.out.println(brand.get(car)); // 奔驰
        // 8. 得到所有属性
        Field[] fields = cls.getFields();
        for (Field f : fields) {
            System.out.println(f.getName());
        }
    }
}

获取Class类对象

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

    • 应用场景:多用于配置文件 读取类全路径 加载类
            String classPath = "com.chenxiii.reflection.Car"; // 一般通过读取配置文件督导
            Class<?> cls1 = Class.forName(classPath);
    
  2. 已知具体的类 通过类的class获取 该方式最为安全可靠 程序性能最高

    • 应用场景:多用于参数传递 比如通过反射得到对应构造器对象
            // 类名.class
            Class cls2 = Car.class;
            System.out.println(cls2); // class com.chenxiii.reflection.Car
            System.out.println(String.class); //class java.lang.String
    
  3. 已知某个类的实例 调用该实例的getClass方法获取Class对象

    • 应用场景:通过创建好的对象 获取Class对象
            Car car = new Car();
            Class cls3 = car.getClass();
            System.out.println(cls3); //class com.chenxiii.reflection.Car
    
  4. 类加载器获取Class对象

            // 通过类加载器(4种)获取
            // 1.得到类加载器
            ClassLoader classLoader = car.getClass().getClassLoader();
            // 2.通过类加载器得到Class对象
            Class cls4 = classLoader.loadClass(classPath);
            System.out.println(cls4); //class com.chenxiii.reflection.Car
    
  5. 基本数据类型获取Class对象

            // 基础数据类型获取Class对象
            Class<Integer> integerClass = int.class;
            Class<Character> characterClass = char.class;
            System.out.println(integerClass); //int
    
  6. 基本数据类型对应的包装类 可以通过 .TYPE 得到Class对象

            // 基础数据类型对应的包装类获取Class对象
            Class<Integer> type = Integer.TYPE;
            System.out.println(type); //int
    

测试实例

public class ClassDemo02 {
    public static void main(String[] args) throws Exception {
        // Class.forName()
        String classPath = "com.chenxiii.reflection.Car"; // 一般通过读取配置文件督导
        Class<?> cls1 = Class.forName(classPath);

        // 类名.class
        Class cls2 = Car.class;
        System.out.println(cls2); // class com.chenxiii.reflection.Car
        System.out.println(String.class); //class java.lang.String

        // 对象.getClass()
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3); //class com.chenxiii.reflection.Car

        // 通过类加载器(4种)获取
        // 1.得到类加载器
        ClassLoader classLoader = car.getClass().getClassLoader();
        // 2.通过类加载器得到Class对象
        Class cls4 = classLoader.loadClass(classPath);
        System.out.println(cls4); //class com.chenxiii.reflection.Car

        System.out.println(cls1.hashCode());//1639705018
        System.out.println(cls2.hashCode());//1639705018
        System.out.println(cls3.hashCode());//1639705018
        System.out.println(cls4.hashCode());//1639705018

        // 基础数据类型获取Class对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        System.out.println(integerClass); //int

        // 基础数据类型对应的包装类获取Class对象
        Class<Integer> type = Integer.TYPE;
        System.out.println(type); //int

        System.out.println(integerClass.hashCode()); //1627674070
        System.out.println(type.hashCode()); //1627674070
    }
}

哪些类型有Class对象

  1. 外部类 成员内部类 静态内部类 局部内部类 匿名内部类
  2. 接口
  3. 数组
  4. 枚举
  5. 注解
  6. 基本数据类型
  7. void
public class ClassDemo03 {
    public static void main(String[] args) throws Exception {
        Class<String> cls1 = String.class; //外部类
        Class<Serializable> cls2 = Serializable.class; //接口
        Class<Integer[]> cls3 = Integer[].class; //数组
        Class<float[][]> cls4 = float[][].class; //二维数组
        Class<Deprecated> cls5 = Deprecated.class; //注解
        Class<Thread.State> cls6 = Thread.State.class; //枚举
        Class<Long> cls7 = long.class; //基本数据类型
        Class<Void> cls8 = void.class; //void
        Class<Class> cls9 = Class.class; //Class类

        System.out.println(cls1);
        System.out.println(cls2);
        System.out.println(cls3);
        System.out.println(cls4);
        System.out.println(cls5);
        System.out.println(cls6);
        System.out.println(cls7);
        System.out.println(cls8);
        System.out.println(cls9);
    }
}

image-20210928163632070

类加载

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

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

    public class ClassDemo04 {
        public static void main(String[] args) throws Exception {
            String key = "1";
            switch (key) {
                case "1":
                    Dog dog = new Dog(); //静态加载
                    dog.cry();
                    break;
                case "2":
                    System.out.println("ok");
                    break;
            }
        }
    }
    
    

    image-20210928164727964

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

    public class ClassDemo04 {
        public static void main(String[] args) throws Exception {
            String key = "1";
            switch (key) {
                case "1":
                    //Dog dog = new Dog();//静态加载
                    //dog.cry();
                    System.out.println("case 1");
                    break;
                case "2":
                    // 反射 -> 动态加载
                    Class<?> cls = Class.forName("Person");
                    Object o = cls.newInstance();
                    Method method = cls.getMethod("hi");
                    method.invoke(o);
                    System.out.println("case 2");
                    break;
            }
        }
    }
    

    image-20210928165044787

类加载的时机:

  1. 当创建对象时(静态加载)
  2. 当子类被加载时 父类也加载(静态加载)
  3. 调用类中的静态成员时(静态加载)
  4. 通过反射(动态加载)

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

验证阶段:

  1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求 并且不会危害虚拟机自身的安全
  2. 包括:文件验证格式(是否以魔数 0xcafebabe开头) 元数据验证 字节码验证和符号引用验证
  3. 可以考虑用 -Xverify:none 参数来关闭大部分的类验证措施 缩短虚拟机类加载的时间

准备阶段:JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值) 这些变量所使用的内存都将在方法中进行分配

class A {
    public int n1 = 10; // 实例属性 不是静态变量 在准备阶段不分配内存
    public static int n2 = 10; // 静态变量 会分配内存 n2的值是默认初始化0
    public static final int n3 = 10; // 是static final 是常量 和静态变量不一样 一旦赋值就不变 n3=30
}

解析阶段:虚拟机将常量池内的符号引用替换为直接引用的过程

初始化阶段:

  1. 到初始化阶段 菜真正开始执行类中定义的Java程序 此阶段是执行()方法的过程

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

    public class ClassDemo05 {
        public static void main(String[] args) throws Exception {
            // 1.加载B类 并生产B的class对象
            // 2.连接 num = 0
            // 3.初始化 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句 并进行合并
            /*
                clinit() {
                    System.out.println("B的静态代码块");
                    //num = 300;
                    num = 100;
                }
             */
            // 4.执行构造器
            B b = new B();
        }
    }
    
    class B {
        static {
            System.out.println("B的静态代码块");
            num = 300;
        }
        static int num = 100;
    
        public B() {
            System.out.println("构造器B被执行");
        }
    }
    
  3. 虚拟机会保证每一个类的()方法在多线程环境中被正确的加锁 同步 如果多个线程同时去初始化一个类 那么只会有一个线程去执行这个类的()方法 其他线程都需要阻塞等待 知道活动线程执行()完毕

image-20210928193700553

获取类结构信息

        // 得到class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Cat");
        //获取全类名
        System.out.println(cls.getName());
        //获取简单类名
        System.out.println(cls.getSimpleName());
        // 获取所有public修饰的属性 包含本类和父类的
        Field[] fields = cls.getFields();
        for (Field field : fields) {
            System.out.print(field.getName() + "\t");
        }
        System.out.println();
        // 获取本类所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.print(declaredField.getName() + "\t");
        }
        System.out.println("------------");
        //获取所有public修饰的方法 包含本类和父类的
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        System.out.println("------------");
        // 获取本类所有方法
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.getName());
        }
        System.out.println("------------");
        // 获取本类所有public修饰的构造器
        Constructor<?>[] constructors = cls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }
        System.out.println("------------");
        // 获取本类所有构造器
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getName()); // 只输出名字
        }
        System.out.println("------------");
        // 得到包信息
        System.out.println(cls.getPackage());
        System.out.println("------------");
        // 以Class形式返回父类
        Class<?> superclass = cls.getSuperclass();
        System.out.println(superclass);
        System.out.println("------------");
        // 以class[]的形式返回接口信息
        Class<?>[] interfaces = cls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println(anInterface.getName());
        }
        System.out.println("------------");
        // 以Annotation[]的形式返回注解
        Annotation[] annotations = cls.getAnnotations();
        System.out.println(annotations);
        // 得到class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Cat");
        // 获取本类所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // getModifiers
            // 默认=0 public=1 private=2 protected=4 static=8 final=16 public+static=1+8=9
            System.out.println(declaredField.getName() + "\t"
                    + declaredField.getModifiers() + "\t" 
                    +declaredField.getType());
            /*
                name	1	class java.lang.String
                weight	4	double
                master	0	class java.lang.String
                age	2	int
             */
        }
        // 得到class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Cat");
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            // getModifiers
            // 默认=0 public=1 private=2 protected=4 static=8 final=16 public+static=1+8=9
            System.out.println(declaredMethod.getName() + "\t"
                    + declaredMethod.getModifiers() + "\t"
                    + declaredMethod.getReturnType());
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println(parameterType);
            }
        }
        /*
                f2	4	void
                int
                f3	0	void
                f4	2	void
                hi	1	void
                f1	1	void
                class java.lang.String
         */
        // 得到class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Cat");
        // 获取本类所有构造器
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getName()); // 只输出名字
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("构造器形参类型: "+parameterType);
            }
        }
        /*
        com.chenxiii.reflection.Cat
        构造器形参类型: class java.lang.String
        com.chenxiii.reflection.Cat
        构造器形参类型: int
        com.chenxiii.reflection.Cat
         */

通过反射创建对象

方式一:调用类中的public修饰的无参构造器

方式二:调用类中的指定构造器

public class ReflectCreateInstance01 {
    public static void main(String[] args) throws Exception{
        // 1.先获取User类的Class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.User");
        // 2.通过public的无参构造器创建实例
        Object o = cls.newInstance();
        System.out.println(o);
        // 3.通过public的有参构造器创建实例
        // constructor代表public User(String name)这个构造器
        Constructor<?> constructor = cls.getConstructor(String.class);
        Object o2 = constructor.newInstance("萨科");
        System.out.println(o2);
        // 4.通过非public的有参构造器创建实例
        // 得到构造器对象
        Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class, int.class);
        // 暴破 可以使用反射访问private构造器
        declaredConstructor.setAccessible(true);
        // 创建实例
        Object faker = declaredConstructor.newInstance("Faker", 100);
        System.out.println(faker);
    }
}

通过反射访问类中的成员

访问属性

public class ReflectAccessProperty {
    public static void main(String[] args) throws Exception{
        // 1.得到Student类的Class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Student");
        // 2.创建对象
        Object o = cls.newInstance(); //o的运行类型是Student
        System.out.println(o.getClass());
        // 3.使用反射得到age属性对象
        Field age = cls.getField("age");
        age.set(o, 88); //通过反射操作属性
        System.out.println(o); //Student{age=88, name='null'}
        System.out.println(age.get(o)); //88
        // 4.使用反射操作name属性对象
        Field name = cls.getDeclaredField("name");
        name.setAccessible(true); //暴破
        name.set(o, "Faker");
        System.out.println(o);//Student{age=88, name='Faker'}
        name.set(null, "Uzi");//name是static的 和实例变量无关 因此o可以写成null
        System.out.println(o);//Student{age=88, name='Uzi'}
    }
}

访问方法

public class ReflectAccessMethod {
    public static void main(String[] args) throws Exception{
        // 1. 得到Boss类的Class对象
        Class<?> cls = Class.forName("com.chenxiii.reflection.Boss");
        Object o = cls.newInstance();
        // 2. 调用public的hi()
        Method m1 = cls.getDeclaredMethod("hi", String.class);
        // 3. 调用方法
        m1.invoke(o, "Faker");
        // 4. 调用private static的方法say()
        Method m2 = cls.getDeclaredMethod("say", int.class, String.class, char.class);
        m2.setAccessible(true); //暴破
        System.out.println(m2.invoke(o, 100, "Doinb", '男')); //100 Doinb 男
        // 因为say方法是静态的 所以Object o处可以传入null
        System.out.println(m2.invoke(null, 50, "Uzi", '男')); //50 Uzi 男
        // 5. 在反射中 如果方法有返回值 统一返回Object 运行类型和方法定义的返回类型一致
        Object invoke = m2.invoke(null, 50, "Uzi", '男');
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值