前言
本篇简介:
- 反射概述
- 反射具体功能实现
- Android中的反射应用
一、反射(Reflection)概述
1.定义
是指在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;并且对于任何一个对象,都能够调用它的任何一个方法和属性;这种动态获取信息以及动态调用对象方法的功能就叫做反射。
例如在下面的入口函数中,就可以看到 HashMapClass 里所有的方法。
import java.lang.reflect.Method;
import java.util.HashMap;
public class HashMapClass extends HashMap {
/**
* @param args
*/
public static void main(String[] args) {
Method[] methods = HashMapClass.class.getMethods();
for(Method method:methods){
System.out.println("method name is:"+method.getName());
}
}
}
静态加载和动态加载
Java初始化一个类的时候可以用new 操作符来初始化,也可通过Class.forName的方式来得到一个Class类型的实例,然后通过这个Class类型的实例的newInstance来初始化.我们把前者叫做JAVA的静态加载,把后者叫做动态加载。有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。
静态加载的时候如果在运行环境中找不到要初始化的类,抛出的是NoClassDefFoundError,它在JAVA的异常体系中是一个Error.
动态态加载的时候如果在运行环境中找不到要初始化的类,抛出的是ClassNotFoundException,它在JAVA的异常体系中是一个checked异常,在写代码的时候就需要catch.
2.优点和缺点
优点:运行期类型的判断,动态类加载,动态代理使用反射。
缺点:性能是一个问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。
总结:Java的反射机制在平时的业务开发过程中很少使用到,但是在一些基础框架的搭建上应用非常广泛
3.应用场景
在Java程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型。
编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定
如:
Person p=new Student();
其中编译时类型为Person,运行时类型为Student。
除此之外,程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。
- 逆向代码 ,例如反编译
- 与注解相结合的框架 例如Retrofit
- 单纯的反射机制应用框架 例如EventBus 2.x
- 动态生成类框架 例如Gson
之前项目里面,jni里面,(C语言要操作java的某个实例用到反射 )要拿到java某个类的实例,用反射把它实例化,c语言去调用java,通过反射去实例化java代码中某个类,然后去调用它的方法
同样,我们在解析xml文件的时候,一个类之所以可以让它显示到界面,在xml里面得到全类名,通过反射把它实例化,因为任何一个类通过实例化才能把它显示。
以合适的方式使用反射,会让我们写代码的方式更加灵活。反射使用不当,反而会适得其反,会对性能造成影响。但是EventBus,Retrofit 的如此火爆,让我们有理由相信,对性能的影响也许没那么大,或者说面对现如今硬件配置堪比电脑的手机,这点影响也许可以忽略不计。
4.Class对象
要想使用反射,首先需要获得待操作的类所对应的Class对象。Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。
之前的方法:
Person p=new Person();
在内存中新建一个Person的实例,对象p对这块内存地址进行引用
获取Class对象的三种方式(使用反射机制实现):
(1)使用Class类的静态方法:比如Class.forName("类的全路径");(最常用)
Class clazz=Class.forName("com.it.Person");//forName(包名.类名)
Person p=(Person)clazz.newInstance();
- 通过JVM查找并加载指定的类(上面的代码指定加载了com.it包中的Person类)
- 调用newInstance()方法让加载完的类在内存中创建对应的实例,并把实例赋值给p
(2)使用类的.class语法:比如String.class;
Class clazz=Person.Class();
Person p=(Person)clazz.newInstance();
- 获取指定类型的Class对象,这里是Person
- 调用newInstance()方法在让Class对象在内存中创建对应的实例,并且让p引用实例的内存地址
(3)使用对象的getClass()方法。
Person p=new Person();
Class clazz=p.getClass();
Person p2=(Person)clazz.newInstance();
- 在内存中新建一个Person的实例,对象p对这个内存地址进行引用
- 对象p调用getClass()返回对象p所对应的Class对象
- 调用newInstance()方法让Class对象在内存中创建对应的实例,并且让p2引用实例的内存地址
注意
- cls.newInstance()方法返回的是一个泛型T,我们要强转成Person类
- cls.newInstance()默认返回的是Person类的无参数构造对象
- 被反射机制加载的类必须有无参数构造方法,否者运行会抛出异常
5.通过类的不带参数的构造方法来生成对象
两种方式:
(1)先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
Class<?> classType = InvokeTester.class;
//用newInstance()方法,生成新的对象
Object invokeTester = classType.newInstance();
(2)先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newsInstance方法生成(其中InvokeTester 是一个自定义的类,有一个无参的构造方法,也有带参数的构造方法):
Class<?> classType = InvokeTester.class;
//获得Constructor对象,此处获取第一个无参数的构造方法的
Constructor cons=classType.getConstructor(new Class[]{});
//通过构造方法来生成一个对象
Object invokeTester = cons.newInstance(new Object[]{});
6.通过类的带参数的构造方法生成对象
带参数的构造方法,传入字符串和整型
Class<?> classType = InvokeTester.class;
//获得Constructor对象,此处获取第一个无参数的构造方法的
Constructor cons=classType.getConstructor(new Class[]{String.class,int.class});
//通过构造方法来生成一个对象
Object invokeTester = cons.newInstance(new Object[]{"zhangsan",22});
可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要指定名字,而获取Method对象时需要指定名字。
二、反射具体功能实现
clazz内部有哪些方法供我们使用:
示例一:
import java.lang.reflect.Method;
public class DumpMethods {
/**
* 演示Reflection API的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息。
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
// 获得字符串所标识的class对象,在此处传入字符串指定类名,所以参数获取可以是一个运行期行为,可以用
//args[0]
Class<?> forName = Class.forName("java.lang.String");
//返回class对象所对应的类或接口中所有方法的数组(包括私有方法)
Method[] methods = forName.getDeclaredMethods();
//遍历所有方法的声明
for(Method method:methods){
System.out.println(method);
}
}
}
输出结果:
示例二:
public class InvokeTester {
public int add(int params1, int params2) {
return params1 + params2;
}
public String say(String content) {
return content;
}
/**
* 通过反射调用方法
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 常规的执行手段
InvokeTester test = new InvokeTester();
System.out.println("add方法:" + test.add(1, 2));
System.out.println("say方法:" + test.say("我是xiaoming!"));
System.out.println(".........");
// 通过反射的方式(通过反射调用自身类的方法)
// 1.获取class对象,前面是通过Class.forName()方法获取,这里使用类名.class(第二种方式)
Class<?> classType = InvokeTester.class;
//用newInstance()方法,生成新的对象
Object invokeTester = classType.newInstance();
System.out.println(invokeTester instanceof InvokeTester);//true
//通过反射调用方法,首先要获得与该方法对应的Method对象
//第一个参数是方法名,第二个参数是这个方法所需要的参数的class对象的数组
Method addMethod = classType.getMethod("add", new Class[]{int.class,int.class});
//调用目标方法
Object result1 = addMethod.invoke(invokeTester, new Object[]{2,3});
System.out.println(result1);//result1是Integer类型
//调用第二个方法
Method sayMethod = classType.getMethod("say", new Class[]{String.class});
Object result2=sayMethod.invoke(invokeTester, new Object[]{"我是Tom!"});
System.out.println(result2);
}
}
输出结果:
示例三:
public class A {
public void speak(String content){
System.out.println("Hello,"+content);
}
public int add(int params1,int params2){
return params1+params2;
}
}
import java.lang.reflect.Method;
public class TestClassLoader {
/**
* 反射调用A上的方法
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
A a=new A();
//获取class对象
// Class<?> clazz = Class.forName("com.it.demo1.A");
Class<? extends A> clazz = a.getClass();
//生成新的对象实例
Object obj = clazz.newInstance();
Method speakMethod = clazz.getMethod("speak", String.class);
Method addMethod = clazz.getMethod("add", new Class[]{int.class,int.class});
speakMethod.invoke(obj, new Object[]{"我是Tom!"});
speakMethod.invoke(obj, "我是Jim");
Object result = addMethod.invoke(obj, new Object[]{3,5});
System.out.println(result);
}
}
输出结果:
注意到TestClassLoad类上不会有对类A的符号依赖——也就是说在加载并初始化TestClassLoad类时不需要关心类A的存在与否,而是等到main()方法执行到调用Class.forName()时才试图对类A做动态加载;这里用的是一个参数版的forName(),也就是使用当前方法所在类的ClassLoader来加载,并且初始化新加载的类。
反射的好处
可能有人会有疑问,明明直接new对象就好了,为什么非要用反射呢?代码量不是反而增加了?
其实反射的初衷不是方便你去创建一个对象,而是让你在写代码的时候可以更加灵活,降低耦合,提高代码的自适应能力.
怎么样降低耦合度,提高代码的自适应能力?
通过接口实现,但是接口如果需要用到new关键字,这时候耦合问题又会出现
示例四:
public class HeroFactory {
public static void main(String[] args) {
HeroFactory facrty = new HeroFactory();
Hero iroman = facrty.CreateHero("IronMan");
iroman.attach();
}
public hero CreateHero(String name) {
if ((name).equals("IronMan")) {
return new IronMan();
}
if ((name).equals("Hulk")) {
return new Hulk();
}
return null;
}
interface Hero {
public void attach();
}
class IronMan implements Hero {
@Override
public void attach() {
System.out.println("Laser Light");
}
}
class Hulk implements Hero {
@Override
public void attach() {
System.out.println("fist");
}
}
}
假设有1000个不同Hero需要创建,那你打算写1000个 if语句来返回不同的Hero对象?
那么,如果使用反射机制,代码如下:
package com.it.demo1;
public class HeroFactory {
public static void main(String[] args) {
HeroFactory facrty = new HeroFactory();
Hero hero=facrty.CreateHero("com.it.demo1.IroMan");
hero.attack();
}
public Hero CreateHero(String name) {
try {
Class cls = Class.forName(name);
Hero hero = (Hero) cls.newInstance();
return hero;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
class IroMan implements Hero {
@Override
public void attack() {
System.out.println("Laser Light");
}
}
class Hulk implements Hero {
@Override
public void attack() {
System.out.println("Fist");
}
}
interface Hero {
public void attack();
}
利用反射机制进行解耦的原理就是利用反射机制"动态"的创建对象:向CreateHero()方法传入Hero类的包名.类名 通过加载指定的类,然后再实例化对象.
示例五:
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 获取类
Class c = Class.forName("java.lang.String");
// 获取所有的属性
Field[] fields = c.getDeclaredFields();
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(c.getModifiers()) + " class "
+ c.getSimpleName() + "{\n");
// 遍历每一个属性
for (Field field : fields) {
sb.append("\t");// 空格
sb.append(Modifier.toString(field.getModifiers()) + " ");// 获得属性的修饰符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");// 属性的类型的名字
sb.append(field.getName() + ";\n");// 属性的名字+回车
}
sb.append("}\n");
System.out.println(sb);
}
}
输出结果:
三、Android中的反射应用
总结一下,在Android中使用反射非常之慢。为了向用户提供最流畅的用户体验,我们强烈建议:
尽可能避免所有对反射的使用(以及使用了反射的第三方库),尤其是使用类型反射来对Java对象进行序列化操作。
Android FrameWork中的反射:
一个类中的每个成员都可以用相应的反射API的一个实例对象来表示——反射机制。
了解这些,那我们就知道了,我们可以利用反射机制在Java程序中,动态的去调用一些protected甚至是private的方法或类,这样可以很大程度上满足我们的一些比较特殊需求。例如Activity的启动过程中Activity的对象的创建。
ClassLoader和DexClassLoader
上面说到JAVA的动态加载的机制就是通过 ClassLoader 来实现的,ClassLoader 也是实现反射的基石。ClassLoader 是JAVA提供的一个类,顾名思义,它就是用来加载Class文件到JVM,以供程序使用的。
但是问题来了,ClassLoader加载文件到JVM,但是Android是基于DVM的,用ClassLoader 加载文件进DVM肯定是不行的。于是Android提供了另外一套加载机制,分别为 dalvik.system.DexClassLoader 和 dalvik.system.PathClassLoader,区别在于 PathClassLoader 不能直接从 zip 包中得到 dex,因此只支持直接操作 dex 文件或者已经安装过的 apk(因为安装过的 apk 在 cache 中存在缓存的 dex 文件)。而 DexClassLoader 可以加载外部的 apk、jar 或 dex文件,并且会在指定的 outpath 路径存放其 dex 文件。
ClassLoader在Java中的应用(利用反射来调用另一个类中的方法)
DexClassLoader使用场景
上面是是使用的已经安装过的Apk,如果采用未安装过的jar包或者Apk,则实例化 DexClassLoader 的时候把相应路径改为需要加载的jar包或者Apk路径亦可拿到结果。这就使得 DexClassLoader 可以应用在HotFix(热修复),动态加载框架等等 一些基于插件化的架构中。
以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!