Day24.虚拟机类加载机制 、类加载器 、反射

本文详细讲解了Java虚拟机的类加载机制,包括Bootstrap、Extension和ApplicationClassLoader,以及双亲委派加载过程。随后介绍了反射的原理,如何通过字节码获取构造方法、成员变量和方法,并提供了实例代码。最后,通过实际案例演示如何使用反射操作私有属性和调用方法。
摘要由CSDN通过智能技术生成

1 虚拟机类加载机制

  1. 概述:
    如果需要使用某一个类型,虚拟机把描述类的数据从class文件中加载到运行内存,并 对数据进行校验,转换解析和初始化,最终形成可以被java虚拟机直接使用的类型, 这就是虚拟机的类加载机制。
    在这里插入图片描述
  2. 加载机制的过程:
    当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始 化三步来实现对这个类的加载。
    (1)加载:就是指将class文件中的信息读入内存,并为之创建一个Class对象
    注意:任何类被使用时系统都会建立一个Class对象
    (2)连接:
    验证是字节码对象是否有正确的内部结构,检验是否符合官方制定的class文件规范
    (3)初始化:负责为类的静态成员分配内存,并设置默认初始化值
  3. 类加载时机:
    (1)创建类的实例 new Person();
    (2)类的静态成员使用 Person.name;
    (3)使用反射方式来访问类型时
    (4)初始化某个类的子类 (在加载子类时,也需要先对父类类型加载)
    (5)直接使用java.exe命令来运行某个主类

2 类加载器

  1. 概述:类加载器是负责加载类的对象。
    将class文件中的数据加载到运行内存中,并为之生成对应的字节码对象。

  2. 分类:
    (1)Bootstrap ClassLoader引导类加载器
    也被称为根类加载器,负责Java核心类的加载。
    (2)Extension ClassLoader扩展类加载器
    (3)Application ClassLoader系统类加载器
        负责在JVM启动时加载来自java命令的class文件

  3. 类加载器之间的继承关系
    -Bootstrap ClassLoader
         -Extension ClassLoader
              -Application ClassLoader

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

    定义了一个类型Person想要加载该类型,Application 收到加载类的请求,再收到 该请求时,并不是直接加载而是将该请求委派他的父类加载器(Extension ),当 Extension 加载器收到子类委派的请求时,Extension 加载并不是直接加载该类型, 而是委派给他的父类Bootstrap ,当Bootstrap 收到请求时,Bootstrap 加载器如果 不能加载该类型,再将请求返回给子类加载器Extension ,Extension 也不能加载 该请求,再将加载的任务返回给他的子类Application ,Application 可以加载该类 型,就直接加载,如果Application 也不能加载,表示根本没有定义过Person,就表 示加载失败。

    使用了一个类型String,Application 收到加载类的请求,并不是直接加载,将请求 给其父类Extension ,Extension 将请求给其父类Bootstrap ,Bootstrap 收到请求 之后,Bootstrap 加载器可以加载String类型,就直接加载。

3 反射

  1. 概述:
    反射是指在加载类型之后,通过该类的字节码对象来获取需要的信息,然后通过获取到 的信息来实现对应的功能,这种机制就是反射。由于这种动态性,可以极大的增强程序 的灵活性和扩展性。
  2. 获取类字节码对象的方式:(使用反射的前提)
    (1)类名.class
    (2)对象名的.getClass()
    (3)Class.forName(类的全类名)
    张三 23 李四 24 :Person.class(字节码对象)
    小猫 黄色 小狗 黑 色:Animal.class(字节码对象) Class类型

代码

package demos1;

import java.util.Scanner;

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

        //获取Person类的字节码对象
        Class c1 = Person.class;
        Class c2 = new Person().getClass();

        //该方式,加载类型使用的是字符串
        //因为字符串的来源很广泛,所以可以从各地获取字符串
        //以后获取的字符串是哪一个类型,就可以加载哪一个类型(提供扩展性)
        //虽然传递的是一个字符串,但是虚拟机会去本地中寻找有么有该字符串对应的字节码文件,如果有对应的文件,就自动加载
        Class c3 = Class.forName("demos1.Person");
        //因为一个类型只加载一次,所以获取类的字节码对象只有一个
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true
        System.out.println(c3 == c2);//true
    }
}

3.1 反射获取字节码对象中的构造方法并实例化

  1. 概述:根据类的字节码对象获取该类的构造方法,并创造该类对象
  2. 获取构造方法的方式:
函数Value
getConstructors()返回所有公共的构造方法对象
getDeclaredConstructors()返回所有构造方法对象
getConstructor()返回空参构造对象
getConstructor(Class<?>... parameterTypes)返回单个指定参数的公共有参构造方法对象
getDeclaredConstructor(Class<?>...parameterTypes)返回单个指定参数的有参构造方法对象
  1. 通过构造方法对象,创建类的实例:
    newInstance(Object...initargs)
    如果是空参构造,就不需要给出实参
    如果是有参构造对象,就需要给出对应的实际参数

代码

package demos1;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取类的字节码对象
        Class<?> cla = Class.forName("demos1.Person");
        //获取字节码对象中维护的构造方法
        //获取公共的构造方法对象
//        Constructor<?>[] cons = cla.getConstructors();
        //获取所有的构造方法对象
        Constructor<?>[] cons = cla.getDeclaredConstructors();
        for(Constructor con:cons){
            System.out.println(con);
        }
        System.out.println("..............................");
        Constructor<?> con1 = cla.getConstructor();//获取空参构造方法对象
        System.out.println(con1);
        Constructor<?> con2 = cla.getConstructor(String.class, int.class);//获取有参构造方法对象
        System.out.println(con2);
        Constructor<?> con3 = cla.getDeclaredConstructor(String.class);//获取私有的有参构造方法对象
        System.out.println(con3);
        //构造方法对象已经获取,就要使用获取的构造方法创建对象
        Object o = con1.newInstance();
        System.out.println(o);
        Object o2 = con2.newInstance("张三",23);
        System.out.println(o2);
        
    }
}

4 案例

package demos2_test;

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

public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //想要喝什么水果,输入对应的序号即可
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("请录入您要买的果汁序号:");
            System.out.println("1、苹果汁");
            System.out.println("2、橘子汁");
            System.out.println("3、西瓜汁");
            System.out.println("4、关门");
            String str = null;
            int x = sc.nextInt();
            switch(x){
                case 1:
                    str = "demos2_test.Apple";
                    break;
                case 2:
                    str = "demos2_test.Orange";
                    break;
                case 3:
                    str = "demos2_test.WaterMelan";
                    break;
                case 4:
                    return;
            }
            Class<?> cla = Class.forName(str);
            Constructor<?> con = cla.getConstructor();
            Object o = con.newInstance();
            Fruit f = (Fruit)o;
            JuiceMachine jm = new JuiceMachine();
            jm.makeJuice(f);
        }

    }
}

5 反射获取成员变量并使用

  1. 获取方法:
函数Value
getFields()返回所有公共成员变量对象
getDeclaredFields()返回所有成员变量对象
getField(String name)返回指定的公共成员变量对象
getDeclaredField(String name)返回单个成员变量对象
  1. 访问成员属性的方法:
    set(Object obj,Object value): 用于给obj对象中的该成员变量赋value值
    get(Object obj):用于获取obj对象的指定成员变量值
  2. 暴力反射:
    概述:如果类型中的某些属性是私有化的,那么就不能直接使用方法访问该属性
    所以只能使用暴力反射来强制访问。
    相关方法:
    isAccessible():判断当前属性对象是否需要参与虚拟机的检测
    setAccessible(boolean flag):将当前对象设置为不需要被虚拟机检测

代码

package demos1;

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

public class Demo03 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        //使用反射的方式获取类中的成员变量
        Class<?> cla = Class.forName("demos1.Person");
        //获取成员变量,获取的也是一个个对象来表示的属性
//        Field[] fs = cla.getFields();//获取public修饰的属性对象
        Field[] fs = cla.getDeclaredFields();//获取所有的属性对象
        for(Field f:fs){
            System.out.println(f);
        }
        System.out.println("........................");
        //获取某一个属性对象(通过属性的名字来指定,名字定义为一个字符串)
        Field hobby = cla.getField("hobby");
        System.out.println(hobby);
        Field name = cla.getDeclaredField("name");
        System.out.println(name);
        
        System.out.println(hobby.isAccessible());//返回值为false.表示使用当前属性时,需要经过虚拟机检测,如果是公共的就可以使用
        System.out.println(name.isAccessible());//返回值为false,表示使用当前属性时,需要经过虚拟机检测,如果是私有的,不能使用
        name.setAccessible(true);//将当前name属性设置为true之后,后续再使用该属性时,虚拟机不会检测,不管是私有还是公共的都可以直接使用
        //使用空参创建一个对象
        Object o = cla.newInstance();
        //获取该对象中的属性值
        System.out.println(hobby.get(o));
        System.out.println(name.get(o));
        //给对象o的属性赋值
        hobby.set(o,"抽烟喝酒烫头");
        System.out.println(hobby.get(o));
        name.set(o,"于谦");
        System.out.println(name.get(o));
    }
}

6 反射获取成员方法并使用

  1. 方法:
函数Value
getMethods()返回所有公共成员方法对象
getDeclaredMethods()返回所有成员方法对象
getMethod(String methodName, Class<?>...parameterTypes)返回指定的公共成员方法对象
getDeclaredMethod(String methodName, Class<?>...parameterTypes)返回指定的成员方法对象
  1. 执行方法的方式:
    invoke(Object o,Class...paramsTypre)
  2. 如果方法是已经非静态方法,需要传递一个对象执行
  3. 如果方法是一个静态方法,不需要传入对象,直接传入Null

代码

package demos1;

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

public class Demo04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //使用反射获取成员方法并使用
        //先获取类型的字节码对象
        Class cla = Class.forName("demos1.Person");
//        Method[] ms = cla.getMethods();//获取类中公共的方法对象
        Method[] ms = cla.getDeclaredMethods();//获取类中所有的(私有的)方法对象
        for(Method m:ms){
            System.out.println(m);
        }
        System.out.println("....................");
        //使用方法名获取对应的方法对象(名使用字符串传递)
        //如果方法是有参数的,再获取时,需要跟上参数的类型
        Method show1 = cla.getMethod("show");
        System.out.println(show1);
        Method show2 = cla.getMethod("show",String.class);
        System.out.println(show2);
        Method print = cla.getDeclaredMethod("print");
        System.out.println(print);
        Method get = cla.getMethod("get", String.class);
        System.out.println(get);
        System.out.println("........................");
        Object o = cla.newInstance();
        //执行方法
        show1.invoke(o);
        show2.invoke(o,"show2 ");
        int os = ((Integer)get.invoke(o, "123")).intValue();
        System.out.println(os);
        //通过暴力反射的方式,调用私有方法
        print.setAccessible(true);
        print.invoke(o);
       //获取静态方法并使用,在使用时,不需要传入对象,参数给null即可
        Method eat = cla.getMethod("eat");
        eat.invoke(null);
    }
}

7 案例

需求:
定义一个List集合(泛型定义为Integer),如:ArrayList list = new ArrayList<>();
要求利用反射的原理,在集合中添加若干字符串并且不报错,最终输出集合中的内容

package demos1;

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

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        定义一个List集合(泛型定义为Integer),如:ArrayList<Integer> list = new ArrayList<>();
//        要求利用反射的原理,在集合中添加若干字符串并且不报错,最终输出集合中的内容

        ArrayList<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);
        Class<? extends ArrayList> cla = list.getClass();
        //在使用一个带着泛型的类型时
        //正常情况,该类型只能使用泛型定义的参数
        //使用反射,可以越过泛型类型,执行其父类类型(泛型擦除)
        Method add = cla.getMethod("add", Object.class);
        add.invoke(list,"abc");
        add.invoke(list,"xyz");
        System.out.println(list);
    }
}

8 反射练习

  1. 自定义一个Stu类型
    属性:String name; int age (私有化,定义公共的访问方式)
    方法:show(展示当前属性的信息)
  2. 将这个类型的全类名定义在文件中
  3. 读取文件中的类名,并创建一个该类对象(通过有参构造创建)
  4. 使用show方法展示对象属性
  5. 使用公共的访问方法给属性改值,并再次调用show方法展示属性值

代码

package demos1;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        BufferedReader br = new BufferedReader(new FileReader("day24/src/demos1/x.txt"));
        String str = br.readLine();
        Class<?> cla = Class.forName(str);
        Constructor<?> con = cla.getConstructor(String.class, int.class);
        Stu stu = (Stu)con.newInstance("张三", 23);
        Method show = cla.getMethod("show");
        show.invoke(stu);
        Field name = cla.getDeclaredField("name");
        name.setAccessible(true);
        name.set(stu,"张三丰");
        Field age = cla.getDeclaredField("age");
        age.setAccessible(true);
        age.set(stu,100);
        show.invoke(stu);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值