反射的定义

何为反射?

反射机制就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

问题来了————

那这样一来,我的私有变量不就暴露在外面了么?

呃。。。是的,实际上,我们确实能通过反射机制对一个类的私有变量做修改,所以从某种程度上来讲,反射破坏了java的封装性,那么为什么还会作为一种高级特性存在于java中呢?

别急,我们首先看一下反射能为我们做哪些工作。

反射用途

获取

有三种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
//方式一
Person person = new Person();
Class<? extends Person> personClazz01 = person.getClass();

//方式二
try {
Class<?> personClazz02 = Class.forName("Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

//方式三
Class<? extends Person> personClazz03 = Person.class;

通过反射机制得到类的包名和类名

1
2
3
4
5
public static void main(String args[]) {
Person person = new Person();
System.out.println("Test1: 包名: " + person.getClass().getPackage().getName() +
"," + "完整类名: " + person.getClass().getName());
}

核心:通过反射创建类对象

1
2
3
4
5
6
7
Class<?> class1 = null;
class1 = Class.forName("反射.Person");
//由于这里不能带参数,所以你要实例化的这个类Person,一定要有无参构造函数
Person person = (Person) class1.newInstance();
person.setAge(27);
person.setName("yyc");
System.out.println("Test3: " + person.getName() + " : " + person.getAge());

上述是通过反射创建一个无参对象,如果我的构造函数带参的话,需要利用Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Class<?> class1 = null;
Person person1 = null;
Person person2 = null;

class1 = Class.forName("反射.Person");
//得到一系列构造函数集合
Constructor<?>[] constructors = class1.getConstructors();

try {
person1 = (Person) constructors[0].newInstance();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
person1.setAge(28);
person1.setName("yyc");

person2 = (Person) constructors[1].newInstance(29, "yyc");

System.out.println("Test4: " + person1.getName() + " : " + person1.getAge() + " , " + person2.getName() + " : " + person2.getAge());

通过反射调用类方法

1
2
3
4
5
6
7
8
9
10
Class<?> class1 = null;
class1 = Class.forName("反射.SuperPerson");

System.out.println("Test7: \n调用无参方法fly():");
Method method = class1.getMethod("fly");
method.invoke(class1.newInstance());

System.out.println("调用有参方法smoke(int m):");
method = class1.getMethod("smoke", int.class);
method.invoke(class1.newInstance(), 100);

反射原理

说了那么多使用上的例子,我们还是更进一步,看一看系统底层是怎么去做的吧

1
Person person = new Person();

这是我们正常创建一个类的实例,我们从类加载的角度去讲一下它的创建过程,具体过程可以说的很多,可以参考这篇文章JVM类加载

  1. 遇到new,在磁盘中找 Person.class 文件
  2. 如果没有被加载,就在内存中生成一个代表这个类的java.lang.Class对象,所以,class对象只会有一个(在被加载的情况下)。补充:这一部分是通过类加载器实现的 ClassLoader

实际上,这个类加载是在编译时期就完成的,我们称之为“静态加载”,而反射引起的类加载是在运行时期进行的,也就是“动态加载”。

那我们看一看反射:

1
2
class1 = Class.forName("反射.Person");
Person person = (Person) class1.newInstance();

系统根据类的名称直接找到class对象(当然,如果没有的话肯定会触发动态加载),然后通过class对象来对Person对象进行一系列操作(在上述代码,是通过newInstance执行构造函数,创建对象)。此时,Person对象已经完全暴露,可以调用构造函数,或者其他类函数。

如果你没读懂的话,我们来看一下源码是如何做的:

1
2
3
4
5
6
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

具体的内容我们可以不用掌握,但我们需要知道,forName这个会触发类加载器ClassLoader来对输入 String 对应类进行类加载,而这个时机是在运行时进行的。

说一说用途

说了那么多,回到一开始的问题,反射究竟能起到什么作用呢?我们再来回顾一下:我们能通过类名直接访问类中的具体属性和方法。反过来,如果我们不知道类中的具体属性和方法,我们就只能用反射去得到,这种情况在什么时候会出现呢?

那就是当我们使用第三方资源或者框架的时候,我们不知道具体的实现细节,便可以通过反射来了解里面内容,在我们的编译器中,输入一个类名或者一个实例在后面输入一个点“.”,系统会自动弹出其中的所有属性与方法,其实这就是反射。

Android与反射

调用资源

当我们项目需要调用第三方的布局时,我们不能使用setContentView(R.layout.activity_main)方法,我们需要借助反射setContentView(IDHelper.getLayout(getApplicationContext(), "activity_main")),将布局交给工具类,以便在运行时查找。

Activity启动创建

在Manifest文件中四大组件的注册都是用反射来完成的,还有布局文件中的widget等等。 足以见得反射应用之广

总结

现在来看,反射是不是一个很神奇的功能,再回到一开始的问题,其实反射在破坏封装性的同时确实给我们带来了诸多便利。
希望本文对你有所帮助。