zoukankan      html  css  js  c++  java
  • Java 反射

    一、类加载机制

    (一)虚拟机类加载机制

    1、引言:

    虚拟机是运行Java代码的容器, 而实际运行的是对应的class字节码文件,

    因此虚拟机类加载机制就是将class文件加载进入内存, 然后从class文件中获取

    出数据, 并对数据进行【校验,转换解析和初始化】,最终形成可以被java虚拟机

    直接使用的java类型

    2、类的加载机制:一个类, 如果需要使用, 就一定会被加载到内存中, 因此其

    加载过程分为三个步骤:加载, 连接, 初始化

    (二)类加载过程

    1、当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过:加载,

    连接,初始化三步来实现对这个类的加载.

    (1)加载:就是指将class文件读入内存,并为之创建一个Class对象(字节

    码对象)。任何类被使用时系统都会建立一个Class对象

    (2)连接:验证是否有正确的内部结构,并和其他类协调一致(比如父类)。准备负责为

    类的静态成员分配内存,并设置默认初始化值。解析将类的二进制数据中的符号

    引用替换为直接引用

    (3)初始化:就是我们以前讲过的初始化步骤(初始化创建对象)

    2、类的初始化时机:

    (1)创建类的实例

    (2)类的静态成员使用

    (3)类的静态方法调用

    (4)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

    (5)初始化某个类的子类

    (6)直接使用java.exe命令来运行某个主类

    二、类加载器

    (一)类加载器概述

    1、类加载器是负责加载类的对象,将class文件(硬盘)加载到内存中,并为之生

    成对应的java.lang.Class对象

    (二)类加载器分类

    1、Bootstrap ClassLoader 引导类加载器

    也被称为根类加载器,负责Java核心类的加载,比如System,String等.

    2、Extension ClassLoader 扩展类加载器

    负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录.

    3、Application ClassLoader 系统类加载器

    负责在JVM启动时加载来自java命令的class文件,以及classpath环境变

    量所指定的jar包和类路径.

    4、自定义类加载器

    开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以

    满足一些特殊的需求.

    5、类加载器之间的继承关系

    -Bootstrap ClassLoader

    -Extension ClassLoader

    -Application ClassLoader

    (三)双亲委派机制

    1、双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先

    会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己

    的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载

    2、双亲委派模型工作过程:

    (1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己

    去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去

    完成

    (2)当Extension ClassLoader收到一个类加载请求时,他首先也不会自己

    去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成

    (3)如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>lib中未找到所

    需类),就会让Extension ClassLoader尝试加载

    (4)如果Extension ClassLoader也加载失败,就会使用Application

    ClassLoader加载

    (5)如果Application ClassLoader也加载失败,就会使用自定义加载器去

    尝试加载.

    (6)如果均加载失败,就会抛出ClassNotFoundException异常

    3、例子:

    当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首

    先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没加

    有,那么会拿到父载器,然后调用父加载器的loadClass方法。父类中同理会先检

    查自己是否已经加载过,如果没有再往上.注意这个过程,直到到达Bootstrap

    classLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加

    载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会

    抛出ClassNotFoundException

    (四)CLassLoader类

    1、常用方法:

    (1)static ClassLoader getSystemClassLoader() 返回用于委派的系统

    类加载器

    (2)ClassLoader getParent() 返回父类加载器进行委派

    (3)getResourceAsStream(String name) 返回读取指定资源的输入流

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    public class Demo01_ClassLoader {
    
        public static void main(String[] args) throws IOException {
            //根据类加载器,获取流对象,使用流对象可以读取指定的文件
            //getResourceAsStream(String name)
            //可以根据具体的Java类的字节码对象通过getClassLoader() 获取加载这个Java类的类加载器
            //同步锁获取:可以用类的字节码对象,字节码对象:类名.class
            Class<Person> clazz = Person.class;
            //字节码对象表示Person类,可以通过Person类获取加载它的类加载器
            ClassLoader classLoader = clazz.getClassLoader();
            System.out.println(classLoader);
    
            //通过类加载器可以获取到一个关联到指定文件的流对象
            InputStream is = classLoader.getResourceAsStream("config.properties");
    
            //通过流对象加载配置文件信息
            Properties pro = new Properties();
    
            pro.load(is);
    
            String name = pro.getProperty("name");
            String age = pro.getProperty("age");
    
            System.out.println(name + "..." + age);
    
        }
    
        public static void test1() {
            //获取应用类加载器:AppClassLoader -》ApplicationClassLoader
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            System.out.println(loader);
    
            //getParent() 获取当前类加载器的父类加载器
            ClassLoader parent = loader.getParent();
            System.out.println(parent);//ExtClassLoader -> ExtensionClassloader
    
            //尝试获取Bootstrap Classloader
            ClassLoader boot = parent.getParent();
            System.out.println(boot);
        }
    }
    

    三、反射

    (一)反射概述

    1、java反射:在程序运行过程中(动态),可以对任意一个类型进行任意的操

    作。例如:加载任意类型、调用类型的任意方法、获取任意的成员变量、构造方

    法,可以创建该类型的对象。

    2、对于任意一个对象,都能调用这个对象的任意一个方法【不知道要使用什么

    类型】

    3、如果要获取一个类型的各种内容,首先要获取这个类的字节码对象(字节码对

    象属于 Class类型)

    4、解剖这个类型,获取类中的成员,需要使用Class类型中定义的方法

    5、这种【动态】获取信息以及【动态】访问成员的这种方式,称为:反射

    (二)获取类的字节码对象(Class类型对象)的三种方式

    1、要想获取和操作类中的内容,首先要获取类的字节码对象

    2、这些对象(类的字节码对象,也称为.class对象),都是Class类型的对象

    3、获取字节码对象的方式:

    (1)对象名.getClass():返回的是某个引用指向的具体对象所属的运行时

    类,的字节码对象。获取到的是那个真正用来创建对象的类的字节码对象。

    (2)类名.class:如果已经有了类名,可以通过.class的方式获取这个类

    的字节码对象。

    (3)通过Class.forName(String className):Class类中的一个静态方

    法,可以根据一个类的全类名,动态的加载某个类型。传入一个类的全类名,将

    类名描述的字节码文件,加载到内存中,形成一个字节码对象,并且把这个对象

    作为该方法的返回值。(在调用方法之前,是内存中没有这个字节码对象的)。

    字符串的来源非常广泛,来源于代码,可以来源于键盘录入、网络传输、文件读

    取、数据库

    public class Demo02_GetInstanceOfClass {
    
        public static void main(String[] args) throws ClassNotFoundException {
            //方式一:类名.class
            Class clazz = Person.class;
            System.out.println("clazz = " + clazz);
    
            //方式二:使用Object类中的getClass()方法获取
            //任何类型都能继承Object类的getClass方法,即任何类型的对象都能调用此方法获取到调用者对象所属类型本身
            //对象可以通过文件读取到、通过集合获取到、通过new创建、通过传参传入、通过返回值返回...
            Person p = new Person();
            Class<? extends Person> clazz1 = p.getClass();
            System.out.println("clazz1 = " + clazz1);
    
            //方式三:static forName(String className)  通过参数表示的名称,获取到对应的Java类的字节码对象
            Class<?> clazz2 = Class.forName("com.offcn.demos.Person");
            System.out.println("clazz2 = " + clazz2);
        }
    }

    (三)Class类型的理解

    1、Class类型的实例表示正在运行的java应用程序的类或者接口

    2、举例:

    ( 张三,23),(李四, 24),姓名和年龄都不相同,但是都有年龄和姓

    名的描述,因此将姓名和年龄抽取到一个类型中,就形成了Person类型,的概念

    (yellow,4),(white,2),颜色和腿的个数各不相同,但是都有颜色

    和腿个数的描述,因此将颜色和腿个数的描述抽取到一个类型中,就形成了

    Animal类型,的概念

    (姓名, 年龄),(颜色, 腿个数),成员变量和成员方法各不相同,但

    是都有成员变量和成员方法的描述,因此将成员变量和成员方法抽取到一个类型

    中,就形成Class类型,的概念(用于描述类的一个类)

    3、反射的举例:

    房屋设计图纸---->实体房屋,反射:有了房屋,获取设计图纸

    汽车设计图纸---->实体汽车,反射:有了汽车,获取设计图纸

    无论是房屋设计图纸,还是汽车的设计图纸,都是图纸:抽取一个图纸类型

    (四)Class类中的方法

    1、一旦获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都

    定义在Class类型中。

    2、成员方法:获取这个类的各种信息

    成员变量、成员方法、构造方法、内部类、类的注解、类所在的包、类的修

    饰符、判断类的类型

    以上内容获取出来以后,又是一些对象,有专门的数据类型,描述这些对象

    3、其中,有一个非常常用的方法:newInstance()

    创建这个字节码描述的类型的实例对象,例如使用Person.class字节码对

    象,调用newInstance方法,就可以创建一个真正的Person对象

    import java.util.Scanner;
    
    public class Demo03_NewInstance {
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            //利用反射创建Person类的对象
            Scanner sc = new Scanner(System.in);
    
            String className = sc.nextLine();
    
            //1.获取Person类的字节码对象
            Class<?> clazz = Class.forName(className);
    
            //2.为获取到的Java类,创建对象
            Object o = clazz.newInstance();//Object o = new Person();
    
            //3.为对象的成员变量赋值(此处不使用反射,用常规的方式)
            /*Person p = (Person)o;
    
            p.setName("张三");
            p.setAge(23);
    
            System.out.println(p.getName() + "..." + p.getAge());*/
        }
    }
    

    (五)获取类中的构造方法并使用

    1、通过Class类中的:

    getConstructor(Class...paramTypes)

    参数列表:Class...paramTypes,表示一个可变参数,需要传入构造方法的

    类型的字节码对象

    返回值类型:Constructor,表示一个构造方法类型的对象

    2、Constructor类型:

    (1)表示构造方法类型,这个类的每个对象,都是一个确定的,具体的构

    造方法

    (2)构造方法对象应该具有的功能:获取构造方法各种信息(构造方法修

    饰符、构造方法名称、构造方法的参数列表、构造方法的注解),最基本的一个

    功能就是,创建对象

    newInstance(Object...objs)

    返回一个Object类型的对象

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class Demo02_GetConstructor {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //getConstructor(Class<?>... parameterTypes)  获取一个类型的构造方法,是哪个构造方法要通过参数列表指定
            //参数使用了可变参数,即参数的类型是0~无穷个,多个构造,参数的个数肯定都不一样
            //此处可变参数的类型定成了Class,即要书写想要获取的构造方法参数的字节码对象
    
            //反射获取一个类的构造方法,之后为这个类按照构造创建对象
            //1.获取指定类型的字节码对象
            Class<?> clazz = Class.forName("com.xxxx.demos.Student");
    
            //2.获取类的构造方法
            Constructor<?> con = clazz.getConstructor(String.class, int.class);
    
            //3.使用反射获取到的类的构造方法,创建对象
            //newInstance(Object... initargs)
            Object o = con.newInstance("张三", 18);
            //Object o = new Student("张三", 18);
    
            System.out.println("o = " + o);
        }
    }
    

    (六)获取类中的成员变量并使用

    1、通过Class类中的:

    getField(String propertyName)

    参数列表:属性的名称

    返回值类型:Field,表示一个成员变量对象,所属的类型

    2、Field类型:表示一个成员变量类型,每个对象都是一个具体的成员变量

    作用:获取成员变量的各种信息(修饰符、注解、名称);做各种数据类型

    的转换

    赋值:set(Object obj, Object value),用于给obj对象的,该成员变量,

    赋value值

    import java.lang.reflect.Field;
    
    public class Demo03_GrtFields {
    
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
            //getField(String name) 获取公共字段(字段只能是public修饰的)
            //1.获取类(类的字节码对象)
            Class clazz = Student.class;
    
            //2.获取指定名称的字段
            Field field = clazz.getField("score");
    
            //3.为获取到的字段赋值
            //不能直接赋值?因为成员变量随着对象的创建而创建,如果对象没有创建,则成员变量无法赋值
            //创建当前类型的对象
            Object o = clazz.newInstance();
    
            //为对象的这个成员变量赋值
            /*set(Object obj, Object value) 调用者是指定字段(int score),
            第一个参数是拥有调用者字段的对象(new Student),第二个参数是具体的字段值*/
            //动态:运行过程中   反射就是在运行中才能起作用
    
            System.out.println("o = " + o);
    
            field.set(o, 98);
    
            System.out.println("o = " + o);
        }
    }

    (七)获取类中的成员方法并且执行

    1、通过Class类中:

    getMethod(String methodName, Class...paramTypes)

    参数列表:methodName表示方法名称,paramTypes表示参数列表的数据类型

    返回值类型:Method

    2、Method类型:

    (1)表示成员方法的类型,该类型的每个对象,都是一个具体的成员方法

    (2)成员方法对象的方法:获取成员方法信息(方法的注解、方法的修饰

    符、方法的返回值类型、方法的名称、方法的参数)3、其中,最重要的一个方法,就是该方法的执行:

    invoke(Object obj, Object...values)

    在obj对象上,执行该方法,使用的实际参数是values

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class Demo05_GetMethod {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
            //通过反射获取类的方法并操作
            /*
            getMethod(String name, Class<?>... parameterTypes) 获取公共的方法
            name:方法名称
            parameterTypes:写明参数列表
             */
            //1.获取类(的字节码对象)
            Class<?> clazz = Class.forName("com.offcn.demos.Student");
    
            //2.获取类的指定方法
            Method method = clazz.getMethod("setName", String.class);
    
            //3.创建对象,使用反射获取到的方法为这个对象指定的成员变量赋值
            Object o = clazz.newInstance();
    
            /*
            invoke(Object obj, Object... args)
            调用者:获取到的方法对象
            第一个参数:要操作哪个对象
            第二个参数:要为传入的参数
             */
            System.out.println("o = " + o);
    
            method.invoke(o, "老顽童");
    
            System.out.println("o = " + o);
        }
    }

    (八)暴力反射(不推荐)

    1、通过Class类中:

    getDeclaredXxx方法:可以获取类中的所有声明的成员(属性、方法、内部

    类),私有的成员也可以获取到。

    2、修改该对象的访问权限:

    成员变量、构造方法还是成员方法,都是AccessibleObject类型的子类,就

    具有判断是否可以访问,和设置是否可以访问的方法

    isAccessible():判断当前对象是否可以访问

    setAccessible(boolean flag):设定当前对象是否可以访问

    3、一旦设定当前对象可以访问,私有的成员也可以被访问,被修改

  • 相关阅读:
    《leetcode42接雨水》
    《84. 柱状图中最大的矩形》
    [bzoj1565][NOI2009]植物大战僵尸
    [bzoj1497][NOI2006]最大获利
    [洛谷P4092][HEOI2016/TJOI2016]树
    [洛谷P3760][TJOI2017]异或和
    [洛谷P3758][TJOI2017]可乐
    [洛谷P3761][TJOI2017]城市
    [Uva11134]Fabled Rooks
    又是一年叶落时
  • 原文地址:https://www.cnblogs.com/conglingkaishi/p/15092918.html
Copyright © 2011-2022 走看看