java基础-类加载器和反射

赖卓成2023年7月7日
大约 12 分钟

13-1-类加载器和反射

01-类加载器-初步了解

将编译后的class文件加载到jvm虚拟中

image-20230707003742191

02-类加载器-加载过程

类在什么时候加载到内存中呢?

类加载的时机:

  • 创建类的实例
  • 调用类的类方法
  • 访问类或者接口的类变量(静态变量),或者为该变量赋值
  • 使用反射方式强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类(创建子类对象,需要父类的字节码文件也加载到内存中)
  • 直接使用java.exe命令来运行某个主类

总结一句话:用到就加载,不用不加载

03-类的加载过程-加载

image-20230707004142901

加载:

  • 通过一个类的全限定名来获取定义此类的二进制字节流(包名+类名,获取这个类,准备用流进行传输)
  • 将这个字节流所代表的静态存储结构转化为运行时数据结构(把这个类加载到内存中)
  • 在内存中生产一个代表这个类的java.lang.Class对象,任何类被使用时,系统都会为之创建一个java.lang.Class对象(加载完毕,创建class对象)

04-类的加载过程-链接

链接分为三步:验证-准备-解析

验证:确保Class文件字节流中包含的信息符合当前虚拟机的要求,不会损害虚拟机安全。(验证文件是否符合虚拟机规范,是否安全)

准备:负责为类的类变量(静态变量)分配内存,设置默认初始化值。(在刚刚创建的class对象中,初始化静态变量)

解析:将类的二进制数据流中的符号引用替换为直接引用。(如果本类用到了其他的类,此时就需要找到对应的类)

假设加载的是Student类:

image-20230707005159077

起初还不知道String这个类在哪里,先用符号表示:

image-20230707005252017

假设String如下:

image-20230707005335462

Student类的加载过程的解析这一步,就会将符号替换成String的地址:

image-20230707005419829

05-类的加载过程-初始化

初始化:根据程序员通过程序制定的主管计划去初始化类变量和其他资源(静态变量赋值(覆盖原来的默认值)以及初始化其他资源)

小结:

  • 当类使用到的时候,才会加载到内存
  • 类的加载过程:
    • 加载
    • 验证
    • 准备
    • 解析
    • 初始化

06-类加载器-分类

  • 启动类加载器(Bootstrap ClassLoader):虚拟机内置的加载器(C++实现的),虚拟机启动时自动启动。

  • 平台类加载器(Platform ClassLoader):负责加载JDK中一些特殊的模块。

  • 系统类加载器(SysTem ClassLoader):加载用户类路径上指定的类库。

这套课程讲得和网上资料有些不同:点击跳转,其他资料一般说的是:启动类加载器、扩展类加载器、应用程序类加载器、用户自定义类加载器。

查资料后得知:Java9将扩展类加载器改为了平台类加载器

07-类加载器-双亲委派模型

除了启动类加载器之外,其他的加载器都应该有自己的父类加载器

这里说的父类加载器,并不是extends,而是逻辑上的继承

当用户类加载器收到类的加载请求,不会自己去加载,而是一层层往上委托

image-20230707111835547

这些加载器,都有各自的加载范围,当父类加载器所有的加载范围,无法完成类的加载时,就会一层一层往下返回,由子加载器尝试进行加载。

image-20230707112122976

ClassLoader这个类中有静态方法getSystemClassLoader可以获取到系统类加载器,再通过getParent可以获得他的父类加载器(扩展类加载器)

public class ClassLoaderDemo01 {

    public static void main(String[] args) {

        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("systemClassLoader = " + systemClassLoader);

        // 获取系统类加载器的父类加载器 --> 扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println("extClassLoader = " + extClassLoader);

        // 获取扩展类加载器的父类加载器 --> 根加载器(C/C++)
        ClassLoader bootClassLoader = extClassLoader.getParent();
        System.out.println("bootClassLoader = " + bootClassLoader);

        // 测试当前类由哪个类加载器进行加载 --> 系统类加载器
        ClassLoader classLoader = ClassLoaderDemo01.class.getClassLoader();
        System.out.println("当前类是谁加载的:"+classLoader);

        // 测试JDK内置的类是谁加载的 --> 根加载器
        ClassLoader classLoader1 = Object.class.getClassLoader();
        System.out.println("Object是谁加载的:"+classLoader1);

        // 如何获得系统类加载器可以加载的路径
        System.out.println("系统类加载器可以加载的路径"+System.getProperty("java.class.path"));
        
    }
}

image-20230709191319442

08-类加载器-常用方法

image-20230709191958794

public class ClassLoaderDemo02 {

    public static void main(String[] args) {
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        InputStream resourceAsStream = systemClassLoader.getResourceAsStream("prop.properties");
        Properties properties = new Properties();
        try {
            properties.load(resourceAsStream);
            System.out.println(properties);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

image-20230709200320920

image-20230709200437363

09-反射-概述

运行状态中:

对任意一个类,都能够知道这个类的所有属性和方法。

对于一个对象,都能够调用它的任意属性和方法。

可以先获取配置文件中的信息,动态获取信息并创建对象和调用方法。(先写好类,在配置文件指明要使用的类和调用的方法,动态去调用)

利用反射调用它类中的属性和方法时,无视修饰符。

10-反射-获取class对象

在不同的阶段,可以使用不同的方法来获取class对象,有以下三种:

image-20230709201745858

public class Student {
    private String name;

    private Integer age;

    private void study(){
        System.out.println("学生正在学习");
    }

    private Student(String name) {
        this.name = name;
        System.out.println("name = " + name);
        System.out.println("只有name参数的构造");
    }

    private Student(Integer age) {
        this.age = age;

        System.out.println("age = " + age);
        System.out.println("只有age参数的构造");
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("全参构造");
    }

    public Student() {
        System.out.println("无参构造");
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    public static void main(String[] args) throws ClassNotFoundException {

        // 方法1 使用静态方法获取class对象,需要传入的参数为全类名  源码阶段
        Class<?> class1 = Class.forName("com.lzc.ref.ref1.Student");
        System.out.println("class1 = " + class1);

        // 方法2:通过class属性来获取  内存阶段
        Class<Student> class2 = Student.class;
        System.out.println("class2 = " + class2);

        // 方法3:对象阶段
        Student student = new Student();
        Class<? extends Student> class3 = student.getClass();
        System.out.println("class3 = " + class3);

        // 需要注意,一个类只有一个class对象,是唯一的 不管用什么方式获取的,都是同一个

        System.out.println("class1==class2 = " + (class1 == class2));

        System.out.println("(class2==class3) = " + (class2 == class3));
        
    }
}

image-20230709202404576

11-反射-获取Constructor对象

当一个类的字节码加载到内存中以后,内存中记录了以下信息:

image-20230709202558669

通过反射就可以获取这些信息。在Java中,万物皆对象。成员变量就是Field对象,构造方法就是Constructor对象,成员方法就是Method对象

image-20230709202721319

获取Constrictor对象有很多方法,下面写demo:

Student和上一节的一样:

public class Ref02Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

        // 1 获取所有的公共的构造器方法对象数组  获取的是public修饰的,private不会返回
        Class<?> clazz = Class.forName("com.lzc.ref.ref2.Student");
        Constructor<?>[] constructors = clazz.getConstructors();

        for (Constructor<?> constructor : constructors) {
            System.out.println("constructor = " + constructor);
        }

        System.out.println("==========================================================");

        // 2 获取所有的构造器方法的对象数组   获取所有的构造方法对象,返回数组,public 和 private 都能获取
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("declaredConstructor = " + declaredConstructor);
        }

        System.out.println("==========================================================");

        // 3 获取单个公共的构造器方法对象  需要注意的是,getConstructor方法传参和构造器参数一致   只能获取public  不能获取private
        //   如:获取空参构造,就不传入参数
        Constructor<?> constructor1 = clazz.getConstructor();
        System.out.println("constructor = " + constructor1);

        //   如获取全参构造,就和类中定义的一致,传入对应的class对象即可  如果没有对应的,就会报异常
        Constructor<?> constructor2 = clazz.getConstructor(String.class, Integer.class);
        System.out.println("constructor2 = " + constructor2);


        // 4 获取单个构造器方法对象 可以获取所有的,不管是private还是public
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Integer.class);
        System.out.println("declaredConstructor = " + declaredConstructor);
    }
}

运行结果:

image-20230709204827350

13-反射-利用Constructor创建对象

还是一样的Student

public class RefDemo03 {
    public static void main(String[] args) throws Exception {
         method01();

        method02();

        method03();


    }

    /**
     * 获取一个私有的构造器方法对象,并用他调用方法,创建Student对象,期间报错了,因为这个是私有构造
     * 报错描述: class com.lzc.ref.ref3.RefDemo03 cannot access a member of class com.lzc.ref.ref3.Student with modifiers "private"
     * 如何解决:         declaredConstructor.setAccessible(true);
     * @throws Exception 异常
     */
    private static void method03() throws Exception{
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref3.Student");

        // 获取一个私有的构造器方法对象
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Integer.class);

        // 被private修饰的成员不能直接使用,如果需要使用则需要取消访问检查
        declaredConstructor.setAccessible(true);

        // 使用这个构造器对象创建Student对象
        Object o = declaredConstructor.newInstance(25);

        // 强转
        Student student = (Student) o;
        System.out.println("student = " + student);
    }

    /**
     * 空参构造的简写方式,直接调用class对象的newInstance()方法,已经过时,但需要了解
     * @throws Exception 异常
     */
    private static void method02() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref3.Student");

        // 对于空参,可以直接调用class对象中的方法, 该方法已过时
        Object newInstance = clazz.newInstance();

        Student student = (Student) newInstance;
        System.out.println("student = " + student);
    }


    /**
     * 获取public修饰的构造方法对象, 并使用构造方法对象的newInstance创建对象
     *
     * @return {@link Class}<{@link ?}>
     * @throws Exception 异常
     */
    private static Class<?> method01() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref3.Student");

        // 获取全参构造
        Constructor<?> constructor = clazz.getConstructor(String.class, Integer.class);

        // 利用构造器对象创建Student对象
        Object o = constructor.newInstance("赖卓成", 25);

        // 强转
        Student student = (Student) o;
        System.out.println("student = " + student);
        return clazz;
    }
}

image-20230709210541022

14-反射-获取Field对象

上面已经通过反射获取了Contructor对象,并用他创建对应的类的对象,下面来获取成员变量。

  • Field [] getFields():返回所有公共成员变量对象的数组
  • Field[] getDeclaredFields():返回所有成员变量对象的数组
  • Field getField(Sring name):返回单个公共成员变量对象
  • Field getDeclareField(String): 返回单个成员变量对象
public class Student {

    public String name;

    public Integer age;

    public String gender;

    private int money = 300;

}
public class RefDemo04 {

    public static void main(String[] args) throws ClassNotFoundException {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref04.Student");

        // 获取所有的公共属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println("field = " + field);
        }

        // 获取所有的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("declaredField = " + declaredField);
        }

        // 获取指定的属性 公共的
        try {
            Field name = clazz.getField("name");
            System.out.println("name = " + name);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        // 获取指定的属性 私有的 会报错 所以需要使用getDeclaredField
        Field field = null;
        try {
//            field = clazz.getField("money");
            field = clazz.getDeclaredField("money");
            System.out.println("field = " + field);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        
    }

}

image-20230710104335190

15-反射-使用Field对象取值和赋值

  • void set(Object obj,Object value):给指定对象的成员变量赋值
  • Object get(Object obj) 返回指定对象的Field值

实体类和15的一样:

public class RefDemo05 {

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

        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref05.Student");

        // 使用clazz创建对象
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
        Student instance = (Student)declaredConstructor.newInstance();

        // 设置属性name
        clazz.getField("name").set(instance, "lzc");
        System.out.println("instance = " + instance);

        // 获取私有属性money
        Field field = clazz.getDeclaredField("money");
             field.setAccessible(true);
        Object o = field.get(instance);
        System.out.println("o = " + o);
    }
}

image-20230710110148037

16-反射-获取Method对象

  • Method[] getMethods():返回所有的公共方法对象的数组,包括继承的

  • Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承

  • Method getMethod(String name,Class<?> ... parameterTypes) 返回单个公共成员方法对象

  • Method getDeclaredMethod(String name,Class<?> ...parameterTypes) 返回单个成员方法对象

实体类:

public class Student {


    private void show(){
        System.out.println("private - 无参无返回值的show方法");
    }

    public void func1(){
        System.out.println("public - 无参无返回值的func1方法");
    }

    public void func2(String name){
        System.out.println("public - 有参无返回值的func2方法");
        System.out.println("name = " + name);
    }

    public String func3(){
        System.out.println("public - 无参有返回值的func3方法");
        return "func3";
    }

}
public class RefDemo06 {

    public static void main(String[] args) throws Exception{
        method1();

        System.out.println("=====================================");

        method2();

        System.out.println("=====================================");

        method3();

        System.out.println("=====================================");

        method4();

    }

    /**
     * 获取指定的成员方法 可以获取私有的
     *
     * @throws Exception 异常
     */
    private static void method4() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref06.Student");

        // 获取私有的成员方法
        Method show = clazz.getDeclaredMethod("show");

        System.out.println("show = " + show);
    }

    /**
     * 获取指定的公共方法
     *
     * @throws Exception 异常
     */
    private static void method3() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref06.Student");

        // 获取指定的方法(公共的)  参数1 方法名  参数2 方法的参数类型 可变长参数
        Method func2 = clazz.getMethod("func2", String.class);
        System.out.println("func2 = " + func2);
    }

    /**
     * 获取所有成员方法 包括私有的 不包括继承的
     *
     * @throws Exception 异常
     */
    private static void method2() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref06.Student");

        // 获取成员方法对象数组 包括私有的 不包括继承的
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("declaredMethod = " + declaredMethod);
        }
    }

    /**
     * 获取所有公共方法 包括继承的、不包括私有的
     *
     * @throws Exception 异常
     */
    private static void method1() throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref06.Student");

        // 获取所有的成员方法 包括继承的
        Method[] methods = clazz.getMethods();

        // 遍历时除了会输出Student类中的方法,还会输出Object类中的方法
        for (Method method : methods) {
            System.out.println("method = " + method);
        }
    }

}

image-20230710152901827

17-反射-利用Method对象运行方法

  • Object invoke(Object obj,Object ...args):运行方法

参数1:用obj对象调用该方法

参数2:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

写个demo,通过反射获取Student类中的func4(),运行并打印它的返回值:

public class Student {


    private void show(){
        System.out.println("private - 无参无返回值的show方法");
    }

    public void func1(){
        System.out.println("public - 无参无返回值的func1方法");
    }

    public void func2(String name){
        System.out.println("public - 有参无返回值的func2方法");
        System.out.println("name = " + name);
    }

    public String func3(){
        System.out.println("public - 无参有返回值的func3方法");
        return "func3";
    }

    public String func4(String msg){
        System.out.println("public - 有参有返回值的func4方法,参数为:"+msg);
        return "func4";
    }

}

public class RefDemo07 {

    public static void main(String[] args) throws Exception {
        // 获取class对象
        Class<?> clazz = Class.forName("com.lzc.ref.ref07.Student");
        // 获取function4方法
        Method func4 = clazz.getDeclaredMethod("func4",String.class);
        // 创建Student对象
        Student student = (Student) clazz.newInstance();
        // 调用方法
        Object result = func4.invoke(student, "lzc");
        System.out.println("result = " + result);
    }
}

image-20230710214809909

Loading...