什么是反射
“反射”之中包含了一个“反”字,我们先来看看什么是“正”;一般情况下,我们使用某个类会知道他是干什么的,里面有哪些属性和方法,在使用时对这个类实例化,之后用实例化的对象来操作。
反射,则是一开始我们不知道要使用的类是什么,所以不能使用new关键字来实例化,更不会知道类中包含了哪些方法、属性。这时候就需要使用JDK提供的“反射”API进行反射调用。
反射就是在运行时才知道要操作的类是什么,在运行时获取完整的构造,属性,并调用方法。
反射也是Java语言被视为动态语言的关键。
Class类
Class是一个类,封装了当前对象所对应的类的信息。我们写的所有的类,其实都是一个Class的对象,Class就是来描述所有的类。类的所有信息,会在编译的时候加在java类文件的末尾,包括它的构造,方法,属性。
获取Class类
获取Class类的方法有三种:
- 通过类名获取 —— 类名.Class
- 通过对象名获取 —— 对象名.Class
- 通过全类名获取 —— Class.forName(“com.xxx.xxx”)
通过new关键字创建的对象,和通过Class创建的对象是完全一样的:
Person p = new Person("shy",23);
p.getAge();
Log.e("通过new创建Person", "name = " + p.getName());
//通过下面 三种方式创建的Class对象是一样的
//Class class1 = Person.class;
//Class class2 = p.getClass();
try { //全类名可能找不到 android里需要try catch
Class class3 = Class.forName("com.example.study.Person");
Person person = (Person) class3.newInstance();
person.setName("SHY");
Log.e("通过Class创建Person", "name = " + person.getName());
} catch (Exception e) {
e.printStackTrace();
}
输出:
这里要说明一下,在Java中类的信息(构造,属性,方法)都有对应的类来表示(Field:属性,Constructor:构造,Method:方法)。通过这些类的对象,我们可以获取到类的信息。
通过反射获取构造方法
首先我们先创建一个Person类,里面包含一些属性和方法
public class Person {
String name;
private int age;
public Person(){
super();
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void printAll(){
Log.e("printAll被调用","printAll被调用");
}
}
获取构造方法的思路,根据上面的图片,我们可以得知Constructor对象包含在Class中,所以我们获取到类的Class对象,在获取Constructor即可
try {
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("获取全部构造函数","——————————");
Constructor<Person>[] constructors = (Constructor<Person>[]) c.getConstructors();
for (Constructor<Person> constructor : constructors){
Log.e("构造函数:", "" + constructor);
}
} catch (Exception e) {// 为了方便我就直接写Exception了
e.printStackTrace();
}
输出结果:
我们通过getConstructors可以获取到类的全部构造函数,那如何获取指定构造?
//通过getDeclaredConstructor方法 传入想获取的构造函数对应的参数类型即可
//这里不再需要封装类型,基础类型直接 .class 即可
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("获取指定构造函数", "——————————");
Log.e("构造函数:", "" + constructor);
输出结果:
现在已经可以获取构造方法,那么该如何调用呢?
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("获取指定构造函数", "——————————");
Log.e("构造函数:", "" + constructor);
Person person = constructor.newInstance("Shy", 23);
Log.e("调用构造函数之后","name = " + person.getName());
Log.e("调用构造函数之后","age = " + person.getAge());
输出结果:
通过反射获取方法
获取方法和获取构造方法大体上思路是一样的,就是个别方法会有所不同,同样需要先获取Class对象,通过Class对象获取Method
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("获取全部方法","不包括私有方法!!!");
Method[] methods = c.getMethods();
for (Method method : methods){
Log.e("方法:", "" + method);
}
输出结果:
通过上面的代码,会发现Person类中printAll()
方法并没有被打印出来,这就说明getMethods()
能获取除私有方法外的所有方法,那么如何获取私有方法呢?
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("获取当前类的方法","不包括父类方法!!!");
Method[] methods = c.getDeclaredMethods();
for (Method method : methods){
Log.e("方法:", "" + method);
}
输出结果:
通过getDeclaredMethods()
方法能够获取当前类的所有方法,包括私有方法,但是父类的方法无法获取。
当然,和获取构造方法同样,也可以根据参数获取指定方法,调用getDeclaredMethod(name, paramType)
方法可以获取指定方法。
//需要传入 参数名 参数类型, 如果没有参数可以省去
Method method = c.getDeclaredMethod("printAll");
Log.e("获取指定方法", "如果有参数,后面需要传入对应的参数类型");
Log.e("指定方法:", "" + method);
最后来说一下如何调用获取到的方法,再Method类中,有一个invoke方法,即为调用方法。
Method method = c.getDeclaredMethod("printAll");
Log.e("获取指定方法", "如果有参数,后面需要传入对应的参数类型");
Log.e("指定方法:", "" + method);
//要执行方法的对象
Object obj = c.newInstance();
//这里要注意下,如果被调用方法是私有方法必须调用setAccessible设为true!!!
method.setAccessible(true);
//如果调用的方法有参数,后面要跟上参数值
method.invoke(obj);
输出结果:
通过反射获取属性
属性的获取、调用和方法的很相似就不过多赘述了直接上代码
通过getDeclaredFields获得当前类的全部属性,包括私有属性,但不包括父类属性
Log.e("获取当前类属性","不包括父类的属性!!!");
Field[] fields = c.getDeclaredFields();
for(Field field : fields){
Log.e("属性:", "" + field);
}
输出结果:
获取指定属性:
Log.e("获取指定属性","获取指定属性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("获取指定属性name:","" + field.get(person));
输出结果:
修改指定属性:
Log.e("获取指定属性","获取指定属性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("获取指定属性name:","" + field.get(person));
Log.e("修改指定属性","私有属性需要调用setAccessible并且设为true");
//如果属性是私有属性 也需要调用setAccessible(true)
//field.setAccessible(true);
field.set(person, "Shy");
Log.e("修改指定属性name:","" + field.get(person));
输出结果:
静态代理模式
想象一个场景:有一家男装工厂ManFactory
生产男装,一家男装商城ManShop
售卖男装;张三想从这家男装商城购买一件黑色的男装,但是距离太远他没法去,只能通过代购Person
去买,代购还可以给他提供售前售后服务。
根据这样的场景,我们编写一下基础代码:
男装工厂 ManFactory
//男装工厂 生产男装
public interface ManFactory {
public void buyManClothes(String color);
}
男装商城 ManShop
//男装商城售卖男装
public class ManShop implements ManFactory {
//商场买衣服事件
@Override
public void buyManClothes(String color) {
Log.e("男装商城ManShop","买了一件 "+ color +" 男装");
}
}
代购人Person
//代购Person 买衣服可以提供售前 售后服务
public class Person implements ManFactory {
ManFactory manFactory; //必须持有真实对象
public Person(ManFactory manFactory){
this.manFactory = manFactory;
}
/** 售前服务 前置处理器 **/
public void before(){
Log.e("代购Person","售前服务");
}
/** 售后服务 后置处理器 **/
public void after(){
Log.e("代购Person","售后服务");
}
//代购买衣服事件
@Override
public void buyManClothes(String color) {
//售前服务
before();
//代购去商场买衣服
manFactory.buyManClothes(color);
//售后服务
after();
}
}
当张三找代购去购买男装商场的男装时
//张三告诉代购 去哪个商场买
ManFactory manFactory = new ManShop();
Person person = new Person(manFactory);
//代购根据张三的要求 去买 并且提供 售前售后服务
person.buyManClothes("黑色");
输出结果:
以上的代码就完成了简单的静态代理,静态代理的缺点在于,如果此时,李四想要从女装商场买一件女装,他也去找Person代购,这样一来,我们还得去修改Person,让Person去实现WomanFactory,如果男装商场购买条件不是颜色了,改为Int类型的码数了,那改动量就大了。所以静态代理的缺点显而易见,维护性差,可拓展性差。
动态代理模式
为了解决静态代理的种种缺点,动态代理出现了。接着上面的需求,李四又要买女装;我们先创建女装对应的工厂和商场
女装工厂 WomanFactory
//女装工厂
public interface WomanFactory {
public void buyWomanClothes(String color);
}
女装商场 WomanShop
public class WomanShop implements WomanFactory {
@Override
public void buyWomanClothes(String color) {
Log.e("女装商城WomanShop","买了一件 "+ color +" 女装");
}
}
显而易见,如果还去找Person代购,是满足不了需求的,这时就出现了一家代购公司,可以代购多个商场的衣服。
代购公司 Company
public class Company implements InvocationHandler {
Object shop; //持有的真实对象
public Object getShop() {
return shop;
}
public void setShop(Object shop) {
this.shop = shop ;
}
public Object getPoxyInstance(){
//newProxyInstance接受三个参数
//1. 要代理对象的类加载器
//2. 要代理对象的类实现的接口(根据反射得到,反射中没有说获取接口的方式,根据反射获取方法举一反三)
//3. 被代理对象要进行的操作(重写的invoke方法)
return Proxy.newProxyInstance(shop.getClass().getClassLoader(),
shop.getClass().getInterfaces(),
this);
}
/** 售前服务 前置处理器 **/
public void before(){
Log.e("代购公司","售前服务");
}
/** 售后服务 后置处理器 **/
public void after(){
Log.e("代购公司","售后服务");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//售前服务
before();
//去代购 去商场买衣服
Object proxyResult = method.invoke(shop,args);
//售后服务
after();
return proxyResult;
}
}
那么此时,张三李四同时去找代购公司去买衣服用如下代码表示:
//张三要买黑色男装
//告诉代购公司 买哪个商场的衣服
ManFactory manFactory = new ManShop();
Company company = new Company();
company.setShop(manFactory);
//代购公司 派员工 person1 去代购
ManFactory person1 = (ManFactory) company.getPoxyInstance();
//员工 person1 根据张三的要求去买男装 并且完成售前售后服务
person1.buyManClothes("黑色");
//李四 要买红色女装
//告诉代购公司 买哪个商场的衣服
WomanFactory womanFactory = new WomanShop();
company.setShop(womanFactory);
//代购公司 派员工 person2 去代购
WomanFactory person2 = (WomanFactory) company.getPoxyInstance();
//员工 person2 根据李四的要求去买女装 并且完成售前售后服务
person2.buyWomanClothes("红色");
输出结果:
总结一下:学习反射和动态代理模式,更多的在于能够读懂一些主流的框架,很多框架都用动态代理封装了一些操作供我们使用,比如:Retrofit,Retorfit就是将我们定义的网络请求接口通过动态代理翻译成网络请求,再通过okhttp去请求。