装瞧个遍(反射)
版权声明:本文为博主原创文章,未经博主允许不得转载。
今天来看看我们经常能用到的一个知识点,但又不怎么会用的知识点——反射。那我们在什么时候会用到这个知识点呢,以前我们在学习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
这样是不是很简单呢?以前封装就是为了不让访问,现在有了反射,还是可以访问了。赶紧试试吧,小伙伴们。