• 反射机制详解


    0x01、类的加载流程

    当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

    ①加载
    就是指将class文件读入内存,并为之创建一个Class对象。
    任何类被使用时系统都会建立一个Class对象。

    ②连接
    验证 是否有正确的内部结构,并和其他类协调一致
    准备 负责为类的静态成员分配内存,并设置默认初始化值 static{}语句块
    解析 将类的二进制数据中的符号引用替换为直接引用

    ③初始化类型
    创建类的实例
    访问类的静态变量,或者为静态变量赋值
    调用类的静态方法
    使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    初始化某个类的子类
    直接使用java.exe命令来运行某个主类
    .....

    0x02.反射机制入门

    通过上方阅读,当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并为此类创建一个Class对象

    类的加载时机
    
    1. 创建类的实例。
    2. 类的静态变量,或者为静态变量赋值。
    3. 类的静态方法。
    4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
    5. 初始化某个类的子类。
    6. 直接使用java.exe命令来运行某个主类。
    
    public class Test {
        public static void main(String[] args) throws Exception{
            // 类的加载时机
            //  1. 创建类的实例。
            //  Student stu = new Student();
    
            // 2. 类的静态变量,或者为静态变量赋值。
            // Person.country = "中国";
    
            // 3. 类的静态方法。
            // Person.method();
    
            // 4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
            // Class<?> c = Class.forName("com.itheima.demo1_类的加载.Student");
    
            //  5. 初始化某个类的子类。
            // Zi zi = new Zi();
    
            // 6. 直接使用java.exe命令来运行某个主类。
        }
    }
    

    读取到内存,并为次类创建一个Class对象;这时候我们反射的内容开始了,我们第一步,就是通过反射,去获取class对象

    0x03. 获取Class对象并创建对象

    方式1: 通过`类名.class`获得
    方式2:通过`对象名.getClass()`方法获得
    方式3:通过Class类的静态方法获得:` static Class forName("类全名")`
        * 每一个类的Class对象都只有一个。
    

    下面是获得到class对象,这是反射的三种方法,最常见的,是forName获取class对象

    /*
        反射获取class对象的三种方式
     */
    public class Demo3 {
        public static void main(String[] args) throws ClassNotFoundException {
    
            // 1、类名.class
            Class<Student> student1 = Student.class;
            System.out.println(student1);
    
            // 2、对象名.getClass
            Student student = new Student();
            Class<? extends Student> student2 = student.getClass();
            System.out.println(student2);
    
            // 3、 Class.forName("")
            Class<?> student3 = Class.forName("com.itheima_反射.Student");
            System.out.println(student3);
        }
    }
    

    这时候我们需要使用 newInstance() 方法来创建对象

    由于返回值是 OBject类型的,所以我们需要转型

    public class Demo3 {
        public static void main(String[] args) throws Exception {
            // 1、获取Class对象
            Class<?> student = Class.forName("com.itheima_反射.Student");
            // 2、实例化对象
            Student o = (Student) student.newInstance();
    
        }
    }
    

    而这一段代码,可以变为

    public class Demo3 {
        public static void main(String[] args) throws Exception {
            // 等价于  Student student = new Student();
            Student student = (Student) Class.forName("com.itheima_反射.Student").newInstance();
        }
    }
    
    

    0x04、操作构造方法

    因为Student中是有构造方法的,而这边newInstance是默认调用无参构造方法;

    反射之操作构造方法的目的
        * 获得Constructor对象来创建类的对象。
    
    Constructor类概述
        * 类中的每一个构造方法都是一个Constructor类的对象
    

    1. Constructor getConstructor(Class... parameterTypes)
            * 根据参数类型获得对应的Constructor对象。
            * 只能获得public修饰的构造方法
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
            * 根据参数类型获得对应的Constructor对象
        	* 可以是public、protected、(默认)、private修饰符的构造方法。
     3. Constructor[] getConstructors()
            获得类中的所有构造方法对象,只能获得public的
     4. Constructor[] getDeclaredConstructors()
            获得类中的所有构造方法对象
        	可以是public、protected、(默认)、private修饰符的构造方法。
    
    1. T newInstance(Object... initargs)
     	根据指定的参数创建对象
    2. void setAccessible(true)
       设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
    
    import java.lang.reflect.Constructor;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            // 1、获取class对象
            Class<?> student = Class.forName("org.test.反射01.Student");
            // 2、获取Student类的带有三个参数的构造方法,并且参数的类型顺序为:String,String,int
            Constructor<?> user = student.getConstructor(String.class,String.class, int.class);
            // 3、传入参数,并使用 newInstance() 实例化对象,并向下转型
            Student o = (Student)user.newInstance("吴迪", "男", 19);
            System.out.println(o);
    
        }
    }
    

    这是构造方法的public修饰的获取,获取private修饰的构造方法,需要先setAccessible(true),然后再执行

    具体我就不细说了,毕竟用法都一样

    0x05. 操作成员方法

    * Method getMethod(String name,Class...args);
          * 根据方法名和参数类型获得对应的成员方法对象,只能获得public的
    * Method getDeclaredMethod(String name,Class...args);     掌握
           * 根据方法名和参数类型获得对应的成员方法对象,包括public、protected、(默认)、private的
    * Method[] getMethods();
            * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
    * Method[] getDeclaredMethods();    掌握
             * 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的
    
    Method对象常用方法
    *  Object invoke(Object obj, Object... args)
        * 调用指定对象obj的该方法
        * args:调用方法时传递的参数
    *  void setAccessible(true)
        设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消
    

    我们先看看调用没有传参的成员方法
    Studeng.java文件:

    public class Student {
        public void show1(){
            System.out.println("public 修饰的show1方法,无参数...");
        }
    
        public void show1(String str,int num){
            System.out.println("public 修饰的show1方法,2个参数...");
            System.out.println("str:"+str+",num:"+num);
        }
    
        public void show2(){
            System.out.println("public 修饰的show2方法...");
        }
    
        private void show3(){
            System.out.println("private 修饰的show3方法...");
        }
    }
    

    我们这边尝试去调用show1方法

    import java.lang.reflect.Method;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
    
            //获取class对象
            Class<?> clzz = Class.forName("org.test.反射01.Student");
            //实例化对象(默认调用无参构造方法)
            Object o = clzz.newInstance();
            // 通过class对象,调用getMethod方法去获取名为show1()的成员方法,并没有传参
            Method showM = clzz.getMethod("show1");
            showM.invoke(o);
    
    
        }
    }
    
    

    那我们再去调用另外一个show1()方法

    import java.lang.reflect.Method;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //获取class对象
            Class<?> clzz = Class.forName("org.test.反射01.Student");
            //实例化对象(默认调用无参构造方法)
            Object o = clzz.newInstance();
            // 通过class对象,调用getMethod方法去获取名为show1()的成员方法,并没有传参
            Method showM = clzz.getMethod("show1",String.class,int.class);
            showM.invoke(o,"吴一凡",17);
    
    
        }
    }
    
    

    0x05. 操作成员变量

    反射之操作成员变量的目的
        * 通过Field对象给对应的成员变量赋值和取值
    
    Field类概述
        * 每一个成员变量都是一个Field类的对象。
    

    通过反射获取类的成员变量

    Class类中与Field相关的方法
    * Field getField(String name);
        *  根据成员变量名获得对应Field对象,只能获得public修饰
    * Field getDeclaredField(String name);
        *  根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
    * Field[] getFields();
        * 获得所有的成员变量对应的Field对象,只能获得public的
    * Field[] getDeclaredFields();
        * 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的
    

    通过反射访问成员变量

    Field对象常用方法
    void  set(Object obj, Object value) 
    void setInt(Object obj, int i) 	
    void setLong(Object obj, long l)
    void setBoolean(Object obj, boolean z) 
    void setDouble(Object obj, double d) 
    
    Object get(Object obj)  
    int	getInt(Object obj) 
    long getLong(Object obj) 
    boolean getBoolean(Object ob)
    double getDouble(Object obj) 
    
    void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
    Class getType(); 获取属性的类型,返回Class对象。
    

    setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。

    getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

    package org.test.反射01;
    
    import java.lang.reflect.Field;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //获取class对象
            Class<?> clzz = Class.forName("org.test.反射01.Student");
            //实例化对象(默认调用无参构造方法)
            Object stu = clzz.newInstance();
            // 通过class对象,调用getDeclaredField,获取被private修饰名为sex的属性
            Field sexF = clzz.getDeclaredField("sex");
            // 暴力反射
            sexF.setAccessible(true);
            //设置值
            sexF.set(stu,"李四");
            // 获取
            System.out.println(sexF.get(stu));// 李四
    
    
    
        }
    }
    
    

    例子:本地命令执行

    Runtime.getRuntime().exec("calc.exe");
    

    怎么通过反射机制调用呢?
    首先我们要了解运行流程

    也就是说,这边的Runtime.getRuntime(),其实就是Runtime runtime = new Runtime()
    然后再runtime.exec()去执行命令

    import java.lang.reflect.Method;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
    
            //获取Runtime的class对象
            Class clazz = Class.forName("java.lang.Runtime");
            // 获取exec的方法,并有一个参数
            Method execMethod = clazz.getMethod("exec", String.class);
            // 获取getRuntime方法
            Method getRuntimeMethod = clazz.getMethod("getRuntime");
            /*
                执行 getRuntime() 方法,返回runtime对象
                
            */
            Object runtime = getRuntimeMethod.invoke(clazz);
            //最后执行exec方法
            execMethod.invoke(runtime, "calc.exe");
        }
    }
    

    再整合一下,就更短了

    import java.lang.reflect.Method;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
    //        Class clazz = Class.forName("java.lang.Runtime");
    //        clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");
        }
    }
    

    invoke 的作用是执行方法,它的第一个参数是:
    如果这个方法是一个普通方法,那么第一个参数是类对象
    如果这个方法是一个静态方法,那么第一个参数是

  • 相关阅读:
    C#Note13:如何在C#中调用python
    C# Note12:WPF只允许数字的限制性TextBox
    C# Note11:如何优雅地退出WPF应用程序
    C#读书笔记:线程,任务和同步
    Programming好文解读系列(—)——代码整洁之道
    java算法面试题:从类似如下的文本文件中读取出所有的姓名,并打印出重复的姓名和重复的次数,并按重复次数排序 ;读取docx 读取doc 使用poi 相关jar包提集提供下载
    java面试题:如果一串字符如"aaaabbc中国1512"要分别统计英文字符的数量,中文字符的数量,和数字字符的数量,假设字符中没有中文字符、英文字符、数字字符之外的其他特殊字符。
    java算法面试题:有一个字符串,其中包含中文字符、英文字符和数字字符,请统计和打印出各个字符的个数 按值的降序排序,如果值相同则按键值的字母顺序
    java算法面试题:编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串,但要保证汉字不被截取半个, 如“我ABC”,4,应该截取“我AB”,输入“我ABC汉DEF”,6,应该输出“我ABC”,而不是“我ABC+汉的半个”。
    Java算法面试题:编写一个程序,将e: eck目录下的所有.java文件复制到e:jpg目录下,并将原来文件的扩展名从.java改为.jpg
  • 原文地址:https://www.cnblogs.com/0x7e/p/14246855.html
Copyright © 2020-2023  润新知