背景
做Java或者Android开发的攻城狮们应该都听过反射(别想歪了,别开车,兄得)。或者根本就没听过,当有前辈提起说某个功能可以用反射实现,你试一下的时候,很多人和我一样可能就一脸懵逼了。这里我就把自己学习到了关于反射的知识,和大家分享一下。
一、什么是反射
首先,什么是反射,我们为什么要用反射或者什么时候需要用反射呢?反射反射,关键就在于它那个反字上(不是射哦。sorry,又开车啦!),要了解反字,我们需要先知道什么是正,在Android开发中,我们正常的使用某个类时都必然是先知道这是什么类,他能做哪些事。然后我们再通过New的方式实例化一个该类的对象,然后再调用它的内部方法。而反射就完全相反,反射就是从一开始我们就不知道我们需要调用的对象是什么类,甚至都不知道它内部有哪些方法,所以也就不能够通过New的方式来创建一个实例对象了。这时候,我们就只能使用JDK提供给我们的反射API来进行反射调用。通俗的讲就是:反射是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整结构,并能够调用对应的方法。
Java是一个面向对象的语言,我们创建的每一个类都可以看作是一个Class对象。但是我们创建类时并没有显示的去写这个Class对象,那么这个Class对象是放在哪里的呢?当我们创建一个类后,会进行编译,编译器会将我们的类编译成Class文件,而这个Class文件的末尾就保存了这个Class对象,里面保存了这个类的原数据信息(如:类名、方法、变量、修饰符等等)。而我们所调用的方法、变量属性等都在这个Class对象中。反射中最关键的环节之一就是获取这个Class对象,那么Java中如何获取一个类的Class对象呢,获取到这个Class对象后我们又能干什么呢?
二、反射的基本使用
先来讲讲如何使用反射,然后我们再详细剖析为什么要使用反射。
(1)什么是 :正
一般情况下,我们开发时都是这样去创建某个实例对象,然后调用对象的方法完成某种功能,这种直接通过New的方式创建对象的方式就是前面我们说的正:已经知道这是个什么类了,而且对其内部构造一清二楚的情况下直接new对象,然后调用方法就可以完成功能了。
//通常情况下我们这样去创建一个实例
ReflectBean bean = new ReflectBean();
bean.setName("大家好,我是韦小宝");
(2)什么是:反
对于攻城狮们来说,有一般情况就必然存在特殊情况。那么者个特殊情况就是,我们在代码编译期完全不知道将要实例化的对象是属于那个类,更不知道它的内部构造,所以也就不能够直接通过new的方式来创建对象,更不能像正常情况那样来调用对象的内部方法了。那么这时候贴心的JDK为我们提供了反射这种方式,来完成我们的工作。
(3)使用反射的前提
前面我们也说了,每一个类都可以看成是一个Class对象,而使用反射的前提就是获取这个Class对象。那么Java如何去获取一个类的Class对象呢?JDK为我们提供了三种方式。
Class beanClass1 = ReflectBean.class;
Class beanClass2 = bean.getClass();
Class beanClass3 = Class.forName("com.demo.singlecode.reflectdemo.Bean.ReflectBean");
需要特别注意第三种获取Class对象的方式,这也是目前很多框架正在使用的方式,如果研究过Android源码的同学,应该也能经常看到这种用法。
如果只拿到Class对象对于调用者来说没有什么实质性意义,我们需要通过Class对象来获取到类对象才能够调用方法修改属性等。有人是不是又懵了,咋又是Class对象,又是类对象呢?大家可以这样来理解,Class对象只是用来保存这个类的信息,供虚拟机使用,同时我们可以通过这个Class对象创建类对象。而类对象可以看成是一个实体,能够实现某种功能,供调用者直接使用完成任务。
JDK为我们提供了两种通过Class对象来创建类对象的方式
1、通过Class对象的newInstance来获取类对象
ReflectBean newBean = (ReflectBean) beanClass1.newInstance();
newBean.setName("大家好,才是真的好");
2、通过Class对象获取到类的构造器来创建类对象
/**
* 获取这个Class对象所在类的所有构造函数的构造器
*/
Constructor[] constructors = beanClass3.getConstructors();
for(Constructor constructor :constructors){
System.out.println(constructor);
}
/**
* 获取某个带参数的构造函数的构造器
*/
try {
Constructor<ReflectBean> constructor = beanClass3.getConstructor(String.class);
System.out.println(constructor);
//这里需要注意,构造函数里面的参数类型是什么类型,我们就传什么类型的Class,不需要进行转型等操作
Constructor<ReflectBean> constructor2 = beanClass3.getConstructor(String.class,int.class);
System.out.println(constructor2);
/*
*获取到构造器后,如何创建一个对象呢,Class对象可以通过newInstance的方式来创建类对象,构造器同样有类似的方法
*/
ReflectBean bean1 = constructor2.newInstance("爱你呀",18);//需要传入这个构造器所对应的构造函数的实参
System.out.println(bean1.getName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//有人会说这句有问题啊,不是说不知道具体类对象是什么类型吗,怎么还直接强转上了呢?而且还直接调用了内部方法,那我还不如直接去new一个对象来的方便呢。有这想法的同学很棒,因为我这种写法是本末倒置的,只是为了演示如何去获取一个对象。
ReflectBean bean1 = constructor2.newInstance("爱你呀",18)
//反射到了这一步正确的应该是这样的,我们只知道创建出来的是个Object对象。
Object ocject = constructor2.newInstance("爱你呀",18)
现在我们构造器也取到了,也能创建类对象了,我们要如何去调用其内部方法或者修改其内部属性呢?JDK同样为我们提供了API。
/**
*获取所有非私有方法
*/
Method[] methods = beanClass3.getMethods();
for(Method method :methods){
System.out.println(" "+method.getName());
}
/**
* 那么我想获取所有方法,包括私有方法怎么办呢?
*/
Method[] methods1 = beanClass3.getDeclaredMethods();
for(Method method:methods1){
System.out.println(" declare Method :"+method.getName());
}
/*
* 上面都是获取所有方法,那么想获取某个指定方法怎么办呢?
* 第一个参数是,将要获取的方法的方法名,后面是这个方法所对应的参数列表中各个参数所对应的参数类型的Class
*/
try {
Method method = beanClass3.getMethod("setName", String.class);//指定的方法只能是非私有的方法
System.out.println("single public method :"+method.getName());
Method method1 = beanClass3.getDeclaredMethod("setCode");//指定的方法可以是私有方法
System.out.println("single private method :"+method1.getName());
现在方法我们也取到了,Object我们也取到了,那么如何调用这个方法呢,看过源码的应该知道,Method中有一个invoke方法,调用这个invoke方法我们就可以实现对当前Class所在的类中的方法的调用,这也是通过反射调用方法的最后一步
Object object = beanClass3.newInstance();
/**
* 第一个参数是这个类对象的实例,后面是该方法所对应的实参
*/
method.invoke(object,"求包养");
/**
* 如果该方法是私有方法我们必须加上setAccessible(true)打开访问权限
*/
method1.setAccessible(true);
method1.invoke(object);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Java类对象中除了方法,还有字段(如变量、常量等)。如果我想访问或修改这些属性怎么办呢
/**
* 同理,这里只能够获取非私有的字段
*/
Field[] fields = beanClass3.getFields();
/**
* 获取当前类对象的所有字段包括私有字段
*/
Field[] fields1 = beanClass3.getDeclaredFields();
try {
/**
* 获取指定的非私有字段,参数为字段名称
*/
Field field = beanClass3.getField("name");
/**
* 获取指定的字段,包括私有字段,参数为字段名称
*/
Field field1 = beanClass3.getDeclaredField("code");
/**
* 如果我想修改某个字段的值怎么办呢
*/
Object object = beanClass3.newInstance();
/**
* 访问字段
*/
field.set(object,"小慈");
Object value = field.get(object);
System.out.println("public field :"+value);
/**
* 如果该字段是私有字段,我们必须加上setAccessible(true)打开访问权限
*/
field1.setAccessible(true);
field1.set(object,18);
Object value1= field1.get(object);
System.out.println("private field :"+value1);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
需要代码的小伙伴们可以关顾本人github。