美文网首页
你必须掌握的Java基础之反射

你必须掌握的Java基础之反射

作者: 付凯强 | 来源:发表于2018-01-28 17:20 被阅读0次

    0. 序言

    这里只讲解关于反射的基础知识,以后会补充更多的扩展知识,毕竟是基础系列。

    1. 类的加载概述

    • 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
    1. 加载 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
    2. 连接
      1. 验证 是否有正确的内部结构,并和其他类协调一致
      2. 准备 负责为类的静态成员分配内存,并设置默认初始化值
      3. 解析 将类的二进制数据中的符号引用替换为直接引用
    3. 初始化

    2. 类的加载时机

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

    3. 类加载器的概述和分类

    • 3.1 类加载器的概述
      负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
    • 3.2 类加载器的分类
      1. Bootstrap ClassLoader 根类加载器
      2. Extension ClassLoader 扩展类加载器
      3. Sysetm ClassLoader 系统类加载器
    • 3.3 类加载器的作用
      1. Bootstrap ClassLoader 根类加载器
        • 也被称为引导类加载器,负责Java核心类的加载
        • 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
      2. Extension ClassLoader 扩展类加载器
        • 负责JRE的扩展目录中jar包的加载。
        • 在JDK中JRE的lib目录下ext目录
      3. Sysetm ClassLoader 系统类加载器
        • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

    4. 反射概述

    • 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    • 对于任意一个对象,都能够调用它的任意一个方法和属性;
    • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    5. 反射的三种方式

    反射的三种方式.png
            try {
                Class clazz_01 = Class.forName("test.Person");
    
                Class clazz_02 = Person.class;
    
                Person person = new Person("Mr.Fu", 20);
                Class clazz_03 = person.getClass();
    
                System.out.println(clazz_01==clazz_02);
                System.out.println(clazz_02==clazz_03);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    

    6. Class.forName()获取字节码对象和字节码对象通过newInstance创建对象实例

    • 定义一个榨汁机Juicer,每次我想喝苹果汁就new Apple(),每次我想喝橘子汁就new Orange()
    public class test01 {
        public static void main(String[] args) {
            Juicer juicer = new Juicer();
            juicer.run(new Apple());
            juicer.run(new Orange());
        }
    }
    
    class Juicer {
        public void run(Fruit fruit) {
            fruit.squeeze();
        }
    }
    
    interface Fruit {
        void squeeze();
    }
    
    class Apple implements Fruit {
        public void squeeze() {
            System.out.println("榨出一杯苹果汁");
        }
    }
    
    class Orange implements Fruit {
        public void squeeze() {
            System.out.println("榨出一杯橘子汁");
        }
    }
    
    榨出一杯苹果汁
    榨出一杯橘子汁
    
    • 为了不修改代码:创建conifg.properties.txt文件
    test.Apple  //类名
    

    test.Orange
    
    • 修改并读取配置文件即可
    public class test01 {
        public static void main(String[] args) {
            Juicer juicer = new Juicer();
            try {
                // 创建输入流对象,关联配置文件
                BufferedReader reader = new BufferedReader(new FileReader("config.properties.txt"));
                try {
                    //读取配置文件一行内容,获取该类的字节码对象
                    Class clazz = Class.forName(reader.readLine());
                    try {
                        //通过字节码对象创建实例对象
                        Fruit fruit = (Fruit) clazz.newInstance();
                        fruit.squeeze();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    榨出一杯橘子汁
    

    7. getConstructor获取构造方法并使用

    class Person {
    
        private String mName;
        private int mAge;
    
        public Person(String mName, int mAge) {
            this.mName = mName;
            this.mAge = mAge;
        }
    
        public String getmName() {
            return mName;
        }
    
        public int getmAge() {
            return mAge;
        }
    }
    
    public class test01 {
        public static void main(String[] args) {
            try {
                Class clazz = Class.forName("test.Person");
                try {
                   Person person = (Person) clazz.newInstance();
                   System.out.println(person);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    java.lang.InstantiationException: test.Person
        at java.lang.Class.newInstance(Class.java:427)
        at test.test01.main(test01.java:8)
    
    • 以上发现通过newInstance无法创建字节码对象的实例,那是因为newInstance获取的是类的无参构造,但是Person中没有无参构造,所以这个时候要使用getConstructor获取有参构造,通过有参构造创建字节码对象实例:
            Class clazz = null;
            try {
                clazz = Class.forName("test.Person");
                Constructor constructor = null;
                try {
                    //因为还处于反射阶段,所以参数是字节码对象
                    constructor = clazz.getConstructor(String.class, int.class);
                    Person person = (Person) constructor.newInstance("张三", 28);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    

    8. getField和getDeclaredField获取字段

    Class clazz = null;
            try {
                clazz = Class.forName("test.Person");
                Constructor constructor = clazz.getConstructor(String.class, int.class);
                Person person = (Person) constructor.newInstance("张三", 15);
    
                Field field = clazz.getField("mName");
                field.set(person,"李四");
                System.out.println(person);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
    java.lang.NoSuchFieldException: mName
        at java.lang.Class.getField(Class.java:1703)
        at test.test01.main(test01.java:16)
    
    • 以上发现报错,没有mName字段,原因在于我们的字段是私有的,所以我们要用getDeclaredField
    java.lang.IllegalAccessException: Class test.test01 can not access a member of class test.Person with modifiers "private"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
        at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
        at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
        at java.lang.reflect.Field.set(Field.java:761)
        at test.test01.main(test01.java:17)
    
    • 以上发现又报错,说没有权限去修改因为字段是私有的,那只要去除权限了:
    field.setAccessible(true);
    

    9. getMethod或getDeclaredMethod获取方法

    无参:
                Method eat = clazz.getMethod("eat"); //获取
                eat.invoke(person); //执行
                
                我在人民广场吃炸鸡
    
    有参:
                Method eat = clazz.getMethod("eat",int.class);//获取
                eat.invoke(person,10); //执行
    

    10. 通过反射越过泛型检查

    • 向ArrayList<Integer> list 集合中添加字符串
    ArrayList<Integer> list = new ArrayList<>();
                list.add(10);
                list.add(20);
                Class clazz = Class.forName("java.util.ArrayList");
                Method add = clazz.getMethod("add", Object.class);
                add.invoke(list, "abc");
    
                System.out.println(list);
    
    [10, 20, abc]
    

    11. 写一个通用的设置某个对象的某个属性为指定的值

    public class Tool {
        public void setProperty(Object o, String property, Object value) {
            try {
                Class clazz = o.getClass(); //获取字节码对象
                Field declaredField = clazz.getDeclaredField(property); //暴力反射获取字段
                declaredField.setAccessible(true); //去除权限
                declaredField.set(o, value);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    
            Person person = new Person("张三",10);
    
            Tool tool = new Tool();
            tool.setProperty(person,"mName","李四");
    
            System.out.println(person.getmName());
    

    相关文章

      网友评论

          本文标题:你必须掌握的Java基础之反射

          本文链接:https://www.haomeiwen.com/subject/wjrzaxtx.html