反射的具体使用方法

装瞧个遍(反射)

标签: java
384人阅读 评论(2) 收藏 举报
分类:

今天来看看我们经常能用到的一个知识点,但又不怎么会用的知识点——反射。那我们在什么时候会用到这个知识点呢,以前我们在学习SSH框架的时候都在和反射打交道,还记得我们学习SSH框架的时候一直需要配置文件(当然注解就更简单了),这就是频繁的用到了反射。现在很多开源框架都用到反射机制。

还有就是设计模式中的动态代理模式,我们需要在运行的时候才能确定要代理的对象是啥,要代理干什么,这就需要用到反射,在运行期才能够确定下来。

当然还有一些应用就要是在安卓中,可能为了安全起见,有些API方法是被隐藏起来了(有@hide标记的),按正常流程是访问不到的,这时我们也需要用到反射来访问到这个方法。

一、什么是反射机制

简单来说就是,反射机制指的是程序在运行时能够获取自身的信息。

在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。

二、反射机制的优点与缺点

静态编译:在编译时确定类型,绑定对象,即通过。

动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

优点:

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中,它的灵活性就表现的十分明显。

比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了。

当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。

采用静态的话,需要把整个程序重新编译一次才可以实现功能 的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点:

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

三、利用反射机制能获得什么信息

一句话,类中有什么信息,它就可以获得什么信息,不过前提是得知道类的名字,要不就没有后文了,首先得根据传入的类的全名来创建Class对象。

获得构造器的方法

Constructor getConstructor(Class[] params)//根据指定参数获得public构造器

Constructor[] getConstructors()//获得public的所有构造器

Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器

Constructor[] getDeclaredConstructors()//获得所有构造器 

获得类方法的方法

Method getMethod(String name, Class[] params),根据方法名,参数类型获得public方法

Method[] getMethods()//获得所有的public方法

Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法

Method[] getDeclaredMethods()//获得所有的方法 

获得类中属性的方法

Field getField(String name)//根据变量名得到相应的public成员变量

Field[] getFields()//获得类中所有public的成员变量

Field getDeclaredField(String name)//根据方法名获得public和非public成员变量

Field[] getDeclaredFields()//获得类中所有的成员变量 

常用的就这些,知道这些,其他的都好办…… 

首先能调用这些方法,先得获得 Class 对象,那么获取 Class 对象的方式有三种:

①Object 的 getClass()方法

②数据类型的静态属性 class

③Class 中的静态方法

public static void forName(String className)

那么一般使用哪种方式来创建呢?

有两种可能,如果是自己玩,那么可以任选一种,第二种比较方便。

如果是开发,会选择第三种,为什么呢?因为第三种是一个字符串,而不是一个具体的类名,这样的话我们可以把这样的字符串配置在配置文件中去了。

我个人始终认为案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解。

这里新建一个 Student 类,在整个案例中,我们都用到这个 Student 类来讲解

Student.java

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Student {

    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">int</span> age;
    <span class="hljs-keyword">public</span> String address;

    <span class="hljs-keyword">public</span> <span class="hljs-title">Student</span>() {

    }

    <span class="hljs-keyword">private</span> <span class="hljs-title">Student</span>(String name) {
        <span class="hljs-keyword">this</span>.name = name;
    }

    Student(String name, <span class="hljs-keyword">int</span> age) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.age = age;
    }

    <span class="hljs-keyword">private</span> <span class="hljs-title">Student</span>(String name, <span class="hljs-keyword">int</span> age, String address) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.age = age;
        <span class="hljs-keyword">this</span>.address = address;
    }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">show</span>() {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"show"</span>);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span>(String s) {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"method:"</span>+s);
    }
    <span class="hljs-keyword">private</span> String <span class="hljs-title">getString</span>(String s,<span class="hljs-keyword">int</span> i) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"getString:"</span>+s+i;
    }
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">funcation</span>() {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"funcation"</span>);
    }
    @Override
    <span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span>() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Student [name="</span> + name + <span class="hljs-string">", age="</span> + age + <span class="hljs-string">", address="</span>
                + address + <span class="hljs-string">"]"</span>;
    }

}</code>

简单分析一下这个类,这个Student类有三个成员变量 name 、age、address ,权限修饰符分别为 private 、default、public;有四个构造器,无参、一参、两参、三参,权限修饰符分别为 public 、private 、defalut、private;还有四个成员方法,在参数、返回值、权限修饰符各有不同;当然还重写了一个 toString()方法。

案例一:获得 Class 类的对象(三种方式)

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> StudentTestDemo1 {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) throws Exception{
        <span class="hljs-comment">//第一种方法</span>
        Student s = <span class="hljs-keyword">new</span>  Student();
        Class c1 = s.getClass();
        <span class="hljs-comment">//第二种方法</span>
        Class c2 = Student.class;
        <span class="hljs-comment">//第三种方式</span>
        Class c3 = Class.forName(<span class="hljs-string">"com.briup.reflect.Student"</span>);
        System.<span class="hljs-keyword">out</span>.println(c1 == c2);<span class="hljs-comment">//true</span>
        System.<span class="hljs-keyword">out</span>.println(c1 == c3);<span class="hljs-comment">//true</span>
    }
}</code>

结果是 c1 、c2、c3 为同一个对象,这里请记住一句话:所有的类都是 Class 类的对象。即类也是一个对象,它是Class的对象,它的类型称为类类型。

案例二:通过反射去获取构造方法并使用

这里就获取无参的和三参的,因为权限修饰符为 public 和 private ,这两种会了其他的构造方法也就同理了。

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> StudentTestDemo2 {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) throws Exception{
        <span class="hljs-comment">//获取Class对象,字符串为包名+类名</span>
        Class c = Class.forName(<span class="hljs-string">"com.briup.reflect.Student"</span>);

        <span class="hljs-comment">//获取无参构造方法,参数为空,因为无参构造方法没有参数</span>
        Constructor constructor = c.getConstructor();
        <span class="hljs-comment">//根据构造方法创建对象,参数为空,因为是无参构造方法</span>
        Object obj = constructor.newInstance();
        System.<span class="hljs-keyword">out</span>.println(obj);

        <span class="hljs-comment">//获取三参的构造方法,参数是个可变参数,表示构造方法的参数的类型的类类型</span>
        Constructor con = c.getDeclaredConstructor(String.class,<span class="hljs-keyword">int</span>.class,String.class);
        <span class="hljs-comment">//设置取消访问检查,因为权限修饰符为private</span>
        con.setAccessible(<span class="hljs-keyword">true</span>);
        <span class="hljs-comment">//创建对象,参数为构造方法的实参</span>
        Object <span class="hljs-keyword">object</span> = con.newInstance(<span class="hljs-string">"周杰伦"</span>,<span class="hljs-number">30</span>,<span class="hljs-string">"台湾"</span>);
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-keyword">object</span>);
    }
}</code>

结果:

这里写图片描述

从结果我们可以看出,我们获取无参构造方法和三参构造方法并创建出了对象。不过在使用三参的构造方法创建对象之前添加这句代码:con.setAccessible(true);

那么这句代码是表示什么意思呢?

在注释中我们提到这是取消访问检查(我这里把它叫做暴力访问,因为它不管别人的权限修饰符),因为它是 private 的,如果我们不加这句代码去使用的话报异常:java.lang.IllegalAccessException(非法访问异常)

将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

让我们来看看这个方法是在哪呢?是在Constructor中吗?

答:并不是,在 AccessibleObject 类中,AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。

案例三:通过反射获取成员变量并使用

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> StudentTestDemo4 {
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) throws Exception{
        Class c = Class.forName(<span class="hljs-string">"com.briup.reflect.Student"</span>);

        <span class="hljs-comment">//获取指定的成员变量(name属性)</span>
        Field nameField = c.getDeclaredField(<span class="hljs-string">"name"</span>);
        <span class="hljs-comment">//获取指定的成员变量(address属性)</span>
        Field addressField = c.getField(<span class="hljs-string">"address"</span>);

        <span class="hljs-comment">//通过无参构造函数创建对象</span>
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();

        <span class="hljs-comment">//在使用之前要取消访问检查,因为name的权限修饰符为private</span>
        nameField.setAccessible(<span class="hljs-keyword">true</span>);
        <span class="hljs-comment">//给obj对象的nameField即name成员变量赋值为TF</span>
        nameField.<span class="hljs-keyword">set</span>(obj, <span class="hljs-string">"TF"</span>);

        <span class="hljs-comment">//给obj对象的addressField即address成员变量赋值为中国</span>
        addressField.<span class="hljs-keyword">set</span>(obj, <span class="hljs-string">"中国"</span>);
        System.<span class="hljs-keyword">out</span>.println(obj);
    }
}</code>

结果:

这里写图片描述

同理,成员变量和构造方法一样,使用的时候(比如赋值)如果是 private 修饰的话,需要取消访问检查。不然会报异常。

nameField.set(obj, “TF”);这句话的含义为 调用 obj 对象的set() 方法给 nameFiled 成员变量赋值为 TF。

案例四:通过反射获取成员方法并使用

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> StudentTestDemo5 {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) throws Exception {

        Class c = Class.forName(<span class="hljs-string">"com.briup.reflect.Student"</span>);
        <span class="hljs-comment">//获取指定参数的成员方法</span>
        <span class="hljs-comment">//第一个参数表示方法名,第二个参数表示形参的类类型</span>
        Method method = c.getDeclaredMethod(<span class="hljs-string">"getString"</span>,String.class,<span class="hljs-keyword">int</span>.class);

        <span class="hljs-comment">// 通过无参构造方法创建对象</span>
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        <span class="hljs-comment">//取消访问检查</span>
        method.setAccessible(<span class="hljs-keyword">true</span>);
        <span class="hljs-comment">//调用obj对象的method方法,后面跟着的实参,返回值为方法的返回值</span>
        Object <span class="hljs-keyword">object</span> = method.invoke(obj,<span class="hljs-string">"zhou"</span>,<span class="hljs-number">15</span>);

        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-keyword">object</span>);
    }

}
</code>

结果:

这里写图片描述

同理,使用方法之前也需要取消访问检查,因为它的权限修饰符为 private,不然会报异常。

Object object = method.invoke(obj,”zhou”,15); 这句代码表示 调用 obj 对象的method 方法,并传入“zhou”、15这两个实参,object 为方法的返回值。

温馨提示:

①我们每次获取构造函数、成员变量、成员方法的时候要根据它的权限来选择相应的方法。其实我们需要获取的时候只要调用相应的 getDeclaredXXX()即可。因为
getDeclaredXXX()方法获取的是所有的构造函数/成员变量/成员方法(包括public 和 非public 的)

②大家可以看到每次使用构造函数、成员变量、成员方法之前要判断是否要添加取消访问检查,其实我们不用管他们的权限修饰符,每次要使用这些之前都加上取消访问检查不就行了嘛。 是的。

反射的基本方法的使用就介绍到这,当然它不止上面这些方法,我相信,学会上面的方法的使用,你去学习它的其他方法的使用应该是同理的。

总的来说,java反射机制是一个很好用的东西,用它可以解决很多死的东西,因为反射机制的灵活行很大。

前面我们提到了运用反射+配置文件可以非常方便。现在就来模拟一下这中场景:

需求:通过配置文件运行运行类中的方法

前提:

    需要有配置文件配合使用
    用class.txt代替使用
    并且你知道有两个键
        className
        methodName

有三个简单的类:BasketBall、FootBall、PingPang

BasketBall.java

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> BasketBall {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span>() {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"篮球:    科比"</span>);
    }
}
</code>

FootBall.java

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> FootBall {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span>() {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"足球:    C罗"</span>);
    }
}</code>

PingPang.java

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> PingPang {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span>() {
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"乒乓:     张继科"</span>);
    }
}</code>

如果现在现在需要调用BasketBall的方法,那么我们会创建BasketBall的对象,然后再去调用它里面的方法;然后现在需求又改成需要调用FootBall的方法,那么我们需要创建FootBall的对象,再去调用它里面的方法;再然后需求又变成需要调用BasketBall的方法,那么我们又写回去吗?。。。。。。这样周而复始,是不是觉得很繁琐,而且每次都得改动源码,你觉得在客户手中有源码吗?没有,只有.class文件。这样改的话每次都得重新编译,再交到客户手中。

所以我们想到了运用反射,因为反射是在运行中动态获取信息。

配置文件用class.txt代替,内容为:

className=com.briup.reflect.BasketBall

methodName=method
<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> TestDemo {

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) throws Exception {
        Properties prop = <span class="hljs-keyword">new</span> Properties();
        FileReader fr = <span class="hljs-keyword">new</span> FileReader(<span class="hljs-string">"class.txt"</span>); <span class="hljs-comment">//将配置文件加载进来</span>
        prop.load(fr);

        <span class="hljs-comment">//根据键来获取值</span>
        String className = prop.getProperty(<span class="hljs-string">"className"</span>);
        String methodName = prop.getProperty(<span class="hljs-string">"methodName"</span>);

        <span class="hljs-comment">//创建Class对象</span>
        Class c = Class.forName(className);

        <span class="hljs-comment">//获取无参构造方法</span>
        Constructor constructor = c.getDeclaredConstructor();
        <span class="hljs-comment">//不管三七二十一,暴力访问</span>
        constructor.setAccessible(<span class="hljs-keyword">true</span>);
        Object obj = constructor.newInstance();

        <span class="hljs-comment">//获取成员方法</span>
        Method method = c.getDeclaredMethod(methodName);
        <span class="hljs-comment">//不管三七二十一,暴力访问</span>
        method.setAccessible(<span class="hljs-keyword">true</span>);
        method.invoke(obj);
    }
}</code>

这样的话,你需求变了的话,你只需要改一下配置文件的相应的值。重新运行下即可。

如:现在需要调用FootBall的method方法。只需将配置文件改成这样:

className=com.briup.reflect.FootBall
methodName=method

如果又变成需要调用PingPang的method方法。只需将配置文件改成这样:

className=com.briup.reflect.PingPang
methodName=method

这样是不是很简单呢?以前封装就是为了不让访问,现在有了反射,还是可以访问了。赶紧试试吧,小伙伴们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值