反射在平时开发中使用几率较小,但在各大框架中会频繁使用(比如:老版本ButterKnife使用注解与反射初始化控件等,省略findViewById),如果有意向成为架构师,这块知识的掌握必不可少。
一、反射是什么
平时开发中创建对象都是通过 new 关键字创建,通过该对象的实例,可以获取该对象的可访问成员变量或者调用可调用方法,此时我们明确知道使用的类是什么。
那如果我们不知道要初始化的类是什么,就需要使用到JAVA为我们提供的反射API了。
1.1 定义
反射可以在程序的运行时:
- 构造任意一个类的对象
- 了解任意一个对象所属的类
- 了解任意一个类的成员变量和方法
- 调用任意一个对象的属性和方法
这种动态获取程序信息以及动态调用对象的功能称为反射机制。反射是JAVA被视为动态语言的关键。
1.2 原理
在运行时获取到类,但是在运行时.java文件已经在编译阶段被编译成了.class文件,所以反射的原理就是:运行时通过字节码文件获取到类的所有信息。
1.3 优缺点
优点:
- 高自由度:可以无视访问权限限制,被private修饰依然可以调用。
缺点:
- 性能差:反射特别耗时,慢于直接创建对象,所以在使用时要衡量带来的收益是否大于性能的影响。
- 安全性差:反射的高自由度直接导致类的封装性被破坏。
- 通过反射修改代码时,由于是直接操作字节码文件,如果对代码不熟悉,及其容易因为修改而导致报错。
- 反射中有时会直接使用方法名,那在后期维护期间,如果方法名修改被修改,也会产生报错。
二、反射的使用
Class类中方法特别多,我们只以举例的方式写几个常用的例子,大家只需记住通过反射可以获取一个类中所有的成员变量和方法(无视权限),你想要的全都有。
2.1 运行时获取类
从1.2章节反射的原理可以晓得,反射的使用需要先在运行时获取到类,运行时获取到类一共有四种方法,根据情况选择:
1、运行时直接从类中获取
Class<Fruit> fruitClass1 = Fruit.class;
2、运行时从对象中获取对应的类
Fruit fruit = new Fruit();
Class fruitClass2 = fruit.getClass();
3、运行时通过Class类的静态方法获取
Class fruitClass3 = Class.forName("com.kproduce.androidstudy.test.Fruit");
4、通过类加载器获取
Class fruitClass4 = getClassLoader().loadClass("com.kproduce.androidstudy.test.Fruit");
最终这四种方式获取的Class都是相同的。
// 以下结果都是true
System.out.println(fruitClass1 == fruitClass2);
System.out.println(fruitClass2 == fruitClass3);
System.out.println(fruitClass3 == fruitClass4);
2.2 运行时创建对象
通过在2.1中获取的Class类来创建对象。
// 在2.1中拿到的Class类
Class<Fruit> fruitClass = Fruit.class;
// 调用Class类中的方法创建对象
Fruit fruit = fruitClass.newInstance();
2.3 获取构造方法
一个类的构造方法因为参数不同可以很多,所以有API可以直接获取所有构造方法 或者 根据参数不同获取某个构造方法。
// 带有四个构造方法的类
public class Fruit {
public String name;
private int type;
public Fruit() {
}
public Fruit(String name) {
this.name = name;
}
public Fruit(int type) {
this.type = type;
}
public Fruit(String name, int type) {
this.name = name;
this.type = type;
}
}
1、获取所有构造方法:getConstructors()
Constructor<Fruit>[] constructors = (Constructor<Fruit>[]) fruitClass.getConstructors();
2、根据参数获取单个构造方法:getConstructor(参数class…)
// 运行时拿到Class
Class fruitClass = Class.forName("com.kproduce.androidstudy.test.Fruit");
// 1、构造方法:Fruit()
fruitClass.getConstructor();
// 2、构造方法:Fruit(String)
fruitClass.getConstructor(String.class);
// 3、构造方法:Fruit(int)
fruitClass.getConstructor(int.class);
// 4、构造方法:Fruit(String, int)
fruitClass.getConstructor(String.class, int.class);
3、使用构造方法创建对象
// 构造方法:Fruit(String, int)
Constructor<Fruit> constructor = fruitClass.getConstructor(String.class, int.class);
// 根据构造方法 Fruit(String, int) 创建对象
Fruit apple = constructor.newInstance("苹果", 1);
2.4 获取类的所有方法
和获取构造方法类似,有获取所有方法和单个方法的API,但是有两套供选择,注意注释的方法限制。
1、获取所有方法:
- getMethods()
- getDeclaredMethods()
// 获取所有方法,包含从父类继承的,不包括private:
Method[] methods = fruitClass.getMethods();
// 获取所有方法,不包含从父类继承的,包括private:
Method[] declaredMethods = fruitClass.getDeclaredMethods();
2、根据参数获取单个方法:
- getMethod(“方法名”, 参数class…)
- getDeclaredMethod(“方法名”,参数class…)
// 获取单个方法,包含从父类继承的,不包括private,可添加参数(参数重载)
fruitClass.getMethod("方法名", 参数class...);
// 获取单个方法,不包含从父类继承的,包括private,可添加参数(参数重载)
fruitClass.getDeclaredMethod("方法名", 参数class...);
3、使用方法
// 获取方法
Method method = fruitClass.getDeclaredMethod("方法名", 参数class...);
// 如果方法是私有的需要加下面这句
method.setAccessible(true);
// 调用方法,参数是被调用的对象,方法的调用需要基于对象
method.invoke(constructor.newInstance("苹果", 1));
2.5 获取类的成员变量
和获取方法基本一致,也有两套,可以给变量赋值和取值,都是基于对象的。
1、获取所有成员变量:
- getFields()
- getDeclaredFields()
// 获取所有变量,包含从父类继承的,不包括private:
Field[] fields = fruitClass.getFields();
// 获取所有变量,不包含从父类继承的,包括private:
Field[] declaredFields = fruitClass.getDeclaredFields();
2、根据名称获取单个成员变量:
- getField(@NonNull String name)
- getDeclaredField(@NonNull String var1)
// 获取单个成员变量,包含从父类继承的,不包括private
Field nameFiled = fruitClass.getField("name");
// 获取单个成员变量,不包含从父类继承的,包括private
Field typeFiled = fruitClass.getDeclaredField("type");
3、给成员变量赋值和获取值
// 获取值和赋值都是基于对象,所以先创建对象
Fruit apple = constructor.newInstance("苹果", 1);
// 获取name的成员变量
Field nameFiled = fruitClass.getField("name");
// 如果变量是私有的在操作之前需要加下面这句
nameFiled.setAccessible(true);
// 【取值】获取name的值,值是“苹果”
Object filed = nameFiled.get(apple);
// 【赋值】给apple对象,设置name的值为“香蕉”
nameFiled.set(apple, "香蕉");
总结
最后咱们再总结一下反射的知识点:
- 反射可以在程序的运行时,构造任意一个类的对象、了解任意一个对象所属的类、了解任意一个类的成员变量和方法、调用任意一个对象的属性和方法。
- 反射的原理是:运行时通过字节码文件获取到类的所有信息。
- 反射的优点是自由度高,可以无视访问权限限制。缺点是性能差、安全性差(破坏了类的封装性)。
- 反射需要先在运行时得到类,有四种方式,得到类之后可以了解其中的方法和成员变量。
- 反射中对方法的调用、成员变量的取值和赋值,都是基于对象进行操作。
这样反射的介绍就结束了,希望大家读完这篇文章,会对反射有一个更深入的了解。如果我的文章能给大家带来一点点的福利,那在下就足够开心了。