Java基础之反射

0. 序言

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

1. 类的加载概述

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

    1. 加载 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
    2. 连接
    3. 验证 是否有正确的内部结构,并和其他类协调一致
    4. 准备 负责为类的静态成员分配内存,并设置默认初始化值
    5. 解析 将类的二进制数据中的符号引用替换为直接引用
    6. 初始化

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 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());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值