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

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());
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343