什么是反射
我始终认为要彻底理解一个概念,对这个东西出现的前因后果搞搞清楚一定是一个不错的方法。所以我们先来讨论为什么会出现反射这个东西。
想一想,我们平时构造一个对象用的是什么方法?我想,90%的情况下都是这样的:Student student = new Student()
。之所以可以这么做是因为我们可以确定这个场景下我们此时此刻要用的类就是这个 Student 类,更深一步,我们明确地知道这个类里面的哪几个属性是我们需要用到的,然后调用对应的get
和 set
方法即可。但往往,一个系统里肯定不止一个类,因此有时候,我们可能需要一些通用的方法来实现一些相同或相似的功能。举个例子:比如我们的系统中有两个类: Student
和 Teahcer
,它们的定义如下:
Student:
public class Student {
private String sno;
private String name;
@Override
public String toString() {
return "Student{" +
"sno='" + sno + '\'' +
", name='" + name + '\'' +
'}';
}
}
Teacher:
public class Teacher {
private String tno;
private String name;
private Integer salary;
@Override
public String toString() {
return "Teacher{" +
"tno='" + tno + '\'' +
", name='" + name + '\'' +
'}';
}
}
现在想实现这样一个功能:
打印每一个对象的详细信息(调用toString方法)
你可以怎么做呢?
首先,最简单的,你可以编写两个方法: printStudentDetail(Student student)
和 printTeacherDetail(Teacher teacher)
,然后在每一个方法中调用对应类型的toString()
方法即可。但这样会带来一个问题,当我们系统中元数据类型不断增加,对于每一个类型我们都需要编写对应的 print
方法,这是一个很麻烦的工作。因此我们想能不能通过一个方法来实现呢?于是你有了下面的主意:
void printDetail(Object obj){
if (obj instanceof Student){
Student student = (Student) obj;
System.out.println(student.toString());
}
if (obj instanceof Teacher){
Teacher teacher = (Teacher) obj;
System.out.println(teacher.toString());
}
}
通过一个Object类传入对象,然后在方法内部判断这个对象到底是哪一个类的实例对象,然后调用对应的方法。但是还是会面临几乎一样的问题:随着系统中元数据类型不断增加,这个方法中就需要增加更多的判断,代码非常冗杂,毫无优雅可言。
既然这个过程这么麻烦,我们不妨想一想造成这种复杂的根本原因是什么?似乎是因为我们的实例对象并不知道它自己是谁。 如果有一个聪明的对象知道它自己是谁(是哪个类)并且知道自己可以干嘛(这个类中有哪些方法),那么我们让它调用一下自己的打印方法就可以了,不用我们再去判断它的类型,在手动调用对应类中的方法了。举一个不恰当的例子:
某一天一个兄弟找到你让你帮他回家,但是他并不知道自己家在哪里,甚至并不知道自己是谁,你可以怎么做呢?
一个笨办法:你先去北京市户籍部门问一下这兄弟是不是北京人,是的话交给你们了,不是的话那我再去河南省户籍部门问问。如此反复,你需要问34次。但是如果这兄弟知道自己是谁这个问题就好了,直接问他是哪里人,然后把他送到哪里就好啦。
谈到这里,我们的需求很明显了。我们需要一个东西,它可以让每一个对象知道自己是谁。这样的话在一些通用的方法中就可以少掉很多冗余代码。这个东西有吗?有!这就是反射。
现在再来谈反射的概念就很清晰了,我贴一下百度来的概念。
JAVA机制反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的**反射**机制。
现在再来看这个“标准答案”是不是清晰很多了呢。
回到刚才的那个问题。我们利用反射机制,可以让这个对象知道自己是哪个一类,这个类有哪些属性和方法,然后让去调用它的方法就好了。于是就有了下面的代码:
void printDetail(Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = obj.getClass(); //问它到底是哪个类
Method[] methods = clazz.getDeclaredMethods(); //拿到这个类的所有方法
for(Method method : methods){
if (method.getName().equals("toString")){ //如果它有这个方法,就执行它
System.out.println(method.invoke(obj));
}
}
}
通过一个函数,可以实现对所有类的详细信息的打印,真正实现了“通用”的目的。
反射的作用
明白了反射是什么?我们接下来讨论反射可以用来干嘛。简单来说,它可以帮助在代码运行的过程中,动态地判断一个对象所属的类,动态地构造一个对象,动态地判断拿到这个类的属性和方法甚至动态地调用这个类的方法。没错,关键词:“动态”。如果你认为反射仅仅能完成第一节中的那个问题就片面了。实际上,那并不是一个非常常规的反射应用场景。反射的功能非常强大,我们可能会在很多场景下用到它,我这里举几个常规的例子:
Spring框架
没错,如此强大的Spring框架也是应用了反射的原理。行内有一句这样的老话:反射机制是Java框架的基石。一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经封装好了,自己基本用不着写。典型的除了hibernate之外,还有spring也用到很多反射机制。最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
- 使用反射机制,根据这个字符串获得某个类的Class实例
- 动态配置实例的属性
Spring这样做的好处是:
- 不用每一次都要在代码里面去new或者做其他的事情
- 以后要改的话直接改配置文件,代码维护起来就很方便了
- 有时为了适应某些需求,Java类里面不一定能直接调用另外的方法,可以通过反射机制来实现
动态sql
当我们需要写一些复杂查询的时候,可能会涉及到非常多的字段和诸多实体类,如果我们一个属性一个属性装配的话会导致代码非常冗杂。这时候可以使用反射机制,直接拿传入参数中的属性名(比如参数以 Map<String,Object> 的形式接收)去某个对象中查是否有这个属性,有的话就进行生成sql的操作,这样可以帮助我们在写复杂sql的时候省掉100句“cnmd”。
一些其他的说明
本文产生于我工作时的一个小困境,也就是类似于动态生成sql的内容。于是上网学习了关于反射的相关知识。但我看到的大多数文档更加聚焦于反射的语法和代码的使用,而对其如何产生和因何存在的问题的表述都较为笼统,不利于学习者真正理解反射这个东西。本文是我在自己的理解的基础上所做出的一些解释。对于有争议的内容,希望和大家一起讨论,以达到更深层次的理解。而至于语法层面的内容,我始终认为学习者不应该陷入语法的沼泽,因此不做讨论,如有需要可自行查阅。