23.反射(reflection)|Java学习笔记

反射机制

在这里插入图片描述

一个简单的例子:

package com.edu.reflection.question;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectionQuestion {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 根据配置文件 re.properties 指定信息,创建Cat对象并调用方法hi
        // 1.使用Properties 类,读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("D:\\study_folder\\学习笔记\\Java\\韩顺平java\\代码\\chapter23\\src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        System.out.println("classfullpath= " + classfullpath);
        System.out.println("method= " + methodName);
        // 2.创建对象 , 传统的方法,行不通 =》 反射机制

        // 3.使用反射机制解决
        //(1)加载类,返回Class类型的对象cls    (这个类就叫Class,cls是Class类型的一个对象)
        Class<?> cls = Class.forName(classfullpath);
        // (2)通过 cls 可以得到加载的类 com.edu.Cat
        Object o = cls.newInstance();       // @Deprecated(since = "9")
        System.out.println("o的运行类型 = " + o.getClass());   // 得到运行类型
        // (3)通过getMethod 得到加载的类 com.edu.Cat 的 methodName "hi" 的方法对象
        // 即:在反射中,可以把方法视为对象(万物皆对象)
        Method method1 = cls.getMethod(methodName);
        // (4)通过method1调用方法,即通过方法对象来实现调用方法
        System.out.println("=================================");
        method1.invoke(o);  // 反射机制: 方法.invoke(对象);
    }
}

控制台输出结果如下:
classfullpath= com.edu.Cat
method= hi
o的运行类型 = class com.edu.Cat
=================================
hi 招财猫

在这里插入图片描述

Java反射机制原理图

在这里插入图片描述
Java反射机制-十分钟搞懂

Java反射机制可以完成

  1. 在运行时判断任意一个对象所属的类。
  2. 在运行时构造任意一个类的对象。
  3. 在运行时得到任意一个类所具有的成员变量和方法。
  4. 在运行时调用任意一个对象的成员变量和方法。
  5. 生成动态代理。

反射相关的主要类

  1. java.lang.Class:代表一个类, Class对象表示某 个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器

方法:invoke(o);
属性:get(o);
构造方法:newInstance();

这些类在java.lang.reflection

// 这里的 cls 就是上面示例中的 Class<?> cls = Class.forName(classfullpath);

Method method1 = cls.getMethod(methodName);
// (4)通过method1调用方法,即通过方法对象来实现调用方法
System.out.println("=================================");
method1.invoke(o);  // 反射机制: 方法.invoke(对象);

// java.lang.reflect.Field:**代表类的成员变量,Field对象表示某个类的成员变量**
Field NameField = cls.getField("name");     // getField 不能得到私有的属性(受制于访问修饰符)
System.out.println(NameField.get(o));       // 输出了 "招财猫"        反射:成员变量对象.get(对象)

//  java.lang.reflect.Constructor:代表类的构造方法,**Constructor对象表示构造器**
Constructor<?> constructor = cls.getConstructor();  // ()中可以指定构造器参数类型,返回无参构造器
System.out.println(constructor);

Constructor<?> constructor1 = cls.getConstructor(String.class); // 传入的String.class就是String类的Class对象
System.out.println(constructor1);

Java反射getDeclaredConstructor和getConstructor

反射优点和缺点

  1. 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
  2. 缺点:使用反射基本是解释执行,对执行速度有影响

反射调用优化——关闭访问检查

  1. Method 和 Field、 Constructor 对象都有 setAccessible() 方法。
  2. setAccessible 作用是启动 和禁用访问安全检查的开关。
  3. 参数值为 true 表示反射的对象在使用时取消访问检查,提高反射的效率。参数值
    为 false 则表示反射的对象执行访问检查。
Method hi = aClass.getMethod("hi");
hi.setAccessible(true);     // 在反射调用方法时,取消访问
传统方法来调用hi 耗时=3
反射机制来调用hi 耗时=1614
反射机制优化来调用hi 耗时=857

Class类

在这里插入图片描述

  1. Class也是类, 因此也继承Object类 [类图]
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例所生成
  5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API。
  6. Class对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等) https://www.zhihu.com/question/38496907

Class类的常用方法

在这里插入图片描述

获取Class类对象

在这里插入图片描述

package com.edu.reflection.class_;

import com.edu.Car;

public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1. 静态方法Class.forName()
        String classAllPath = "com.edu.Car";
        Class<?> aClass = Class.forName(classAllPath);
        System.out.println(aClass);

        // 2. 类名.class
        System.out.println(Car.class);

        // 3. 对象.getClass()
        Car car = new Car();
        Class<? extends Car> aClass1 = car.getClass();
        System.out.println(aClass1);

        // 4. 通过类加载器来获取到
        ClassLoader classLoader = car.getClass().getClassLoader();  // 先得到类加载器 car
        Class<?> aClass2 = classLoader.loadClass(classAllPath);     // 通过类加载器得到 Class 对象
        System.out.println(aClass2);

        System.out.println(aClass1 == aClass2);     // true  这些aClass其实是同一个对象

        // 5. 基本数据类型得到Class类对象
        Class<Integer> integerClass = int.class;
        System.out.println(integerClass);   // int

        // 6. 基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类对象
        Class<Integer> type = Integer.TYPE;
        System.out.println(type);           // int

        System.out.println(integerClass.hashCode() == type.hashCode()); // true  int.class和Integer.TYPE也是一样的
    }
}

这几种方法获得的对象aClass其实是同一个对象。
int.class 和 Integer.TYPE 也是同一个对象。

哪些类型有Class对象

1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2. interface :接口
3. 数组
4. enum :枚举
5. annotation :注解
6. 基本数据类型
7. void
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;
Class<Class> cls9 = Class.class;
---输出结果如下---
class java.lang.String
interface java.io.Serializable
class [Ljava.lang.Integer;
class [[F
interface java.lang.Deprecated
class java.lang.Thread$State
long
void
class java.lang.Class

类加载

静态加载和动态加载

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

  1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性。

也就是说,静态加载,即使在运行时可能不会被使用到,但是在编译的过程中还是会报错。

package com.edu.reflection.class_;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class ClassLoad_ {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入key:");
        String key = scanner.next();
        switch (key) {
            case "1":
                // 静态加载  依赖性很强
                Dog dog = new Dog();
                dog.cry();
                break;
            case "2":
                // 反射  动态加载
                Class<?> cls = Class.forName("Person");
                Object o = cls.getDeclaredConstructor().newInstance();
                Method m = cls.getMethod("hi");
                m.invoke(o);
                System.out.println("ok");
                break;
            default:
                System.out.println("do nothing...");
        }
    }
}

如果没有编写 Dog 类,就会报错,因为无法通过编译;
但是没有编写 Person 类,也可以通过编译,可就是动态加载的好处。

类加载时机

  1. 当创建对象时(new) // 静态加载
  2. 当子类被加载时,父类也加载 // 静态加载
  3. 调用类中的静态成员时 // 静态加载
  4. 通过反射//动态加载 // 动态加载
    Class.forName(" com.test.Cat");

前三个都是静态加载

类加载过程

在这里插入图片描述

类加载各阶段完成的任务
在这里插入图片描述

加载阶段

在这里插入图片描述

连接阶段——验证

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

在Java中,魔数 oxcafe babe是一个用来标识Java字节码文件的4字节的十六进制数。它出现在每个.class文件的开头,用于在类加载阶段检查该文件是否是标准的Java字节码文件。它的由来是Java的创始人詹姆斯·高斯林在一个叫做死亡咖啡馆的地方吃午饭时想到的。

连接阶段——准备

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

public int n1 = 10;     // n1是实例属性,不是静态变量,因此在准备阶段,不会分配内存
public static int n2 = 20;      // n2是静态变量,分配内存,n2 是默认初始化,是0,而不是20。
public static final int n3 = 30;    // n3是static final是常量,它和静态变量不一样,因为一旦赋值就不变了 n3=30

连接阶段——解析

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

符号引用和直接引用是Java虚拟机在加载和执行类文件时使用的两种不同的引用方式。符号引用是一种抽象的、语义化的引用,它只包含目标的名称、描述符等信息,而不涉及具体的内存地址。例如,一个类文件中对另一个类的方法的调用,就是通过符号引用来表示的,它包含了方法所在的类的全限定名和方法的名称和描述符。直接引用是一种具体的、实现相关的引用,它指向目标在内存中的位置,可以是一个指针、一个偏移量或者一个句柄。例如,当虚拟机解析一个方法调用时,它会把符号引用替换为目标方法在方法区中的起始地址,这就是一个直接引用。
符号引用和直接引用之间的区别主要体现在编译时和运行时。在编译时,字节码文件中只包含符号引用,因为编译器无法知道目标在运行时的具体地址在运行时,虚拟机会根据类加载、链接和初始化等过程,把符号引用解析为直接引用,并根据直接引用来访问目标。这样做的好处是,符号引用可以保证字节码文件的可移植性和灵活性,而直接引用可以保证运行时的高效性和安全性。

Initialization(初始化)

  1. 到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 <clinit>() 方法的过程。
  2. <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句井进行合井。
  3. 虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕。

<clinit>()方法是一个类构造器方法,它是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。它在类加载阶段的初始化时执行,且执行顺序为语句在源文件中出现的顺序,且Java虚拟机会自动保证父类构造器和子类构造器的执行顺序。如果类中没有静态语句和静态代码块,那么可以不生成<clinit>()方法。<clinit>()方法不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物。

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

在这里插入图片描述

通过反射创建对象

重点学习 getConstructorgetDecalaredConstructor之间的区别 ( Java反射getDeclaredConstructor和getConstructor
使用构造器的 newInstance() 方法可以构造对应类的对象
使用构造器的 setAccessible() 方法可以让私有构造器有效
在这里插入图片描述

package com.edu.reflection;

import javax.swing.plaf.nimbus.State;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1. 先获取到User类的Class类
        Class<?> userClass = Class.forName("com.edu.reflection.User");
        // 2. 通过 public 的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);

        // 3.1 通过 public 的有参构造器创建实例
        Constructor<?> constructor = userClass.getConstructor(String.class);
        // 3.2 创建实例,并传入实参
        Object fw = constructor.newInstance("fw");
        System.out.println("fw = " + fw);

        // 4. 通过非 public 的有参构造器创建实例
        // 4.1 得到 private 的构造器对象
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
        // 4.2 创建实例
        declaredConstructor.setAccessible(true);    // 【暴力破解】使用反射可以访问private构造器
        Object user2 = declaredConstructor.newInstance(100, "张三丰");     // 如果直接这样使用的话会报错,需要在前面使用.setAccessible(true);
        System.out.println("user2 = " + user2);
    }
}


class User {
    private int age;
    private String name;

    public User() {     // 无参 public
    }

    public User(String name) {  // public 的有参构造器
        this.name = name;
    }

    private User(int age, String name) {    // private 有参构造器
        this.age = age;
        this.name = name;
    }

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

通过反射访问类中的成员

访问属性

重点学习 getDeclaredField()
暴力破解(使用private属性)同样使用 .setAccessible(true)
修改值使用 f.set(o, value)
获得值使用 f.get(o)

在这里插入图片描述

package com.edu.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class ReflectAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 1. 得到 Student 类对应的 Class对象
        Class<?> stuClass = Class.forName("com.edu.reflection.Student");
        // 2. 创建对象
        Object o = stuClass.getConstructor().newInstance();
        System.out.println(o.getClass());   // class com.edu.reflection.Student
        // 3. 使用反射得到 age 属性对象
        Field age = stuClass.getField("age");
        age.set(o, 88);
        System.out.println(age.get(o));
        // 4. 使用反射操作 name 属性
        Field name = stuClass.getDeclaredField("name");     // 需要使用.getDeclaredField()以及.setAccessible(true)
        name.setAccessible(true);
        name.set(o, "fw");
        System.out.println(name.get(null));     // 由于是静态属性,所以o可以写成null
    }
}

class Student {
    public int age;
    private static String name;

    public Student() {

    }

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

访问方法

和访问属性的方式类似。
在这里插入图片描述

package com.edu.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectAccessMethod {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1. 得到 Boss 类对应的 Class 对象
        Class<?> bossCls = Class.forName("com.edu.reflection.Boss");
        // 2. 创建对象
        Object o = bossCls.getConstructor().newInstance();
        // 3. 调用 public 的 hi 方法
        Method hi = bossCls.getMethod("hi", String.class);
        hi.invoke(o, "easy");
        // 4. 调用 private static 的 say 方法
        Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
        say.setAccessible(true);
        System.out.println(say.invoke(null, 100, "你好", '!'));
    }
}

class Boss {
    public int age;
    private static String name;

    public Boss() {
    }

    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }

    public void hi(String s) {
        System.out.println("hi " + s);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wei *

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

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

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

打赏作者

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

抵扣说明:

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

余额充值