文档章节

Java反射

清风伴月
 清风伴月
发布于 2017/09/03 21:34
字数 1101
阅读 3
收藏 0

这一节课,我们来讲一下反射。反射是Java中非常重要的一个机制。虽然在实际使用中我们很少直接通过反射写代码。但是凡是用过spring的,一定听说过动态代理,动态代理的实现原理中,反射占据了非常重要的地位。还有各种RPC实现,它们的背后也有反射的影子。

反射的概念

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

要理解反射最好的办法就是看代码:

public class TestReflection {
    public static void main(String args[]) {
        try {
            Test obj = Test.class.newInstance();
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String s = br.readLine();

            Method m;
            if (s.equals("sayh")) {
                m = Test.class.getDeclaredMethod("sayHello");
            } else
                m = Test.class.getDeclaredMethod("sayWorld");

            m.invoke(obj);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

class Test {
    public void sayHello() {
        System.out.println("hello");
    }

    public void sayWorld() {
        System.out.println("world");
    }
}

运行这个程序,输入 sayh,就会打印出 hello。

我来解释一下这个程序,首先,我们使用Test.class创建了一个Test类型的实例obj。然后再通过getDeclaredMethod找到Test的某一个方法,如果我们在控制台上输入的是 "sayh",就调用它的sayHello方法,否则就调用sayWorld方法。

我把这个代码,不使用反射再写一遍:

    public static void show() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();

        Test obj = new Test();
        if (s.equals("sayh")) {
            obj.sayHello();
        } else
            obj.sayWorld();
    }

这两个的逻辑是等价的。那我们还要反射干啥呢?

反射的作用

考虑下面的例子:我要调用的函数名和函数参数都是通过IO库读入的,可能是从文件中,也可能是从网络上,而且在调用某个函数之前,我希望打印一行LOG,可想而知,如果是直接写,就会变得很难。但通过反射就会很轻松,我们来演示一下:

    public static void main(String args[]) {
        try {
            Test obj = Test.class.newInstance();
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String s = br.readLine();

            Method m = Test.class.getDeclaredMethod(s, int.class, int.class);
            Integer a = Integer.valueOf(br.readLine());
            Integer b = Integer.valueOf(br.readLine());

            System.out.println("Test." + m.getName() + " has been called with " + a + ", " + b);
            System.out.println(m.invoke(obj, a, b));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

运行结果:

比如说,我要对系统中的每一个函数都加上打印,来跟踪参数的变化和函数的调用情况,如果按照原来直接的写法,那么我真的是只能一个函数一个函数的修改了。但是使用反射呢?我可以在invoke之前统一加。这就带来了很大的方便了。

动态加载类和属性

我们上面看了关于方法的例子,再来看一个动态加载类和属性的例子:

public class TestReflection {
        public static void main(String args[]) {
            try {
                Test obj = Test.class.newInstance();
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                String fieldName = br.readLine();

                Field f = Test.class.getDeclaredField(fieldName);
                f.set(obj, Class.forName("cn.hinus.sort." + br.readLine()).newInstance());

                obj.sort();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    class Test {
        public Sorter sorter;

        public void sort() {
            int[] a = {5, 7, 3, 1, 4, 6, 2};
            sorter.sort(a);
            for(int i : a) {
                System.out.println(i);
            }
        }
}

运行后输入sorter和BubbleSorter,就会看到:

这里,我们使用了,第十二周课程里的排序的例子。这个例子的重点在于,如果我们想在运行时修改sorter的具体实现,我们根本就不需要再去改代码了。直接通过输入参数,就能改变sorter的具体实现。

注意这里面的两个要点:通过getDeclaredField获取 obj 指定的属性,通过Class.forName动态地加载一个类,并通过newInstance动态创建这个类的实例。

好了。今天的讲解就到这里了,关于反射的更多知识,我们下节课再看。

1. 把我们今天的例子的从控制台上读取输入,变成从文件中读取,并尝试一下,只通过修改文本文件的内容就可以改变程序行为,而不必重新编译代码。

2. 学一下,spring的bean的定义,再思考一下这种动态组合对象的能力是怎么做到的。学习完反射,你是不是对依赖注入有了直观的理解?

本文转载自:https://zhuanlan.zhihu.com/p/26032547

共有 人打赏支持
清风伴月
粉丝 1
博文 129
码字总数 255659
作品 0
海淀
程序员

暂无文章

OSChina 周三乱弹 —— 公司女同事约我

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子:分享水木年华的单曲《蝴蝶花(2002年大提琴版)》 《蝴蝶花(2002年大提琴版)》- 水木年华 手机党少年们想听歌,请使劲儿戳(这里) ...

小小编辑
9分钟前
28
5
Linux环境搭建 | VMware下共享文件夹的实现

在进行程序开发的过程中,我们经常要在主机与虚拟机之间传递文件,比如说,源代码位于虚拟机,而在主机下阅读或修改源代码,这里就需要使用到 「共享文件」 这个机制了。本文介绍了两种共享文...

良许Linux
今天
5
0
JUC锁框架——AQS源码分析

JUC锁介绍 Java的并发框架JUC(java.util.concurrent)中锁是最重要的一个工具。因为锁,才能实现正确的并发访问。而AbstractQueuedSynchronizer(AQS)是一个用来构建锁和同步器的框架,使用A...

长头发-dawn
今天
3
0
docker中安装了RabbitMQ后无法访问其Web管理页面

在官网找了"$ docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 rabbitmq:3-management"这条安装命令,在docker上安装了RabbitMQ,,结果输入http://localhost:8080并不......

钟然千落
今天
4
1
spring-cloud | 分布式session共享

写在前面的话 各位小伙伴,你们有福了,这一节不仅教大家怎么实现分布式session的问题,还用kotlin开发,喜欢kotlin的小伙伴是不是很开心! 以前在写Android的时候,就对客户端请求有一定的认...

冯文议
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部