1. 反射的简单例子
我们平时创建一个对象的时候,总是用这样的方法:
Person person = new Person();
这种方式非常自然,是我们一开始学习Java就在用的一种方式。但是考虑一个场景,看这样一段代码:
List<String> list = new ArrayList<>();
...
过了一段时间以后,我发现使用LinkedList更加靠谱,此时我就会去修改代码:
List<String> list = new LinkedList<>();
如果有一天,我发现另外一种List实现更好,我就要继续修改代码。这种方式实在是太不优雅了,也会让我修改的成本变得很高,因为每次都要重新编译打包。
于是我想到了一个办法,就是搞一个参数:
public List<String> getList(String type) {
if (type == "ArrayList") {
return new ArrayList<>();
} else if (type == "LinkedList") {
return new LinkedList<>();
}
}
这样我接收一个参数就好了。比原先的代码要灵活一些,但是没有任何扩展能力——如果我要添加一种新的List实现,我还得加一个if分支出来,还是要修改代码并且要重新编译打包。成本还是很高。
此时就需要用到反射了。将上面的一段代码改造成下面的样子:
private List getList(String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return (List) Class.forName(type).newInstance();
}
这样的话,入参就是具体的实现类,只要是List接口的实现类都可以传进去,传进去什么,就能得到什么。
我们来看看具体的应用:
package tester;
import java.util.List;
public class Main {
public static void main(String[] args) {
try {
List<String> list = getList("java.util.ArrayList");
System.out.println(list.getClass().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
private static List getList(String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return (List) Class.forName(type).newInstance();
}
}
打印的结果就是“java.util.ArrayList”,我们已经成功的达成了目的。
2. 反射的几个用法
要了解反射需要了解这么几个概念:
- Class
- Constructor
- Method
- Field
从名字上就能看出来都是什么,分别是Class对象,构造器,方法和域。
后面几个好理解,每个类都会有这么几个东西,但是Class对象就稍微有点难以理解。
我们回忆一下,javac完成对java文件的编译后生成了什么?就是一个后缀是class的文件。每个类都会产生一个Class对象,这个对象里保存了很多有用的信息,我们只需要知道这一点就够了。
我们所有的操作都要首先依赖这个Class对象,然后才能进行。
现在我们来用几种不同的办法来创建一个Person对象:
package tester;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Application {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
//这是一般寻常的新建对象的办法
Person p1 = new Person();
p1.setId(1);
p1.setName("Tom");
//通过Constructor对象来创建实例
Class clazz = Person.class;
Constructor constructor = clazz.getConstructor();
Person p2 = (Person) constructor.newInstance();
//通过Class对象来找到对象的方法
Method setName = clazz.getMethod("setName", String.class);
Method setId = clazz.getMethod("setId", int.class);
//使用对象
setId.invoke(p2, 2);
setName.invoke(p2, "Tony");
System.out.println(p2);
//通过Class对象来直接创建实例
Class clazz1 = Class.forName("tester.Person");
Person p3 = (Person) clazz1.getDeclaredConstructor().newInstance();
p3.setId(3);
p3.setName("Jerry");
System.out.println(p3);
}
}
可以看出来,反射可以像正常方法一样去创建对象,并调用对象的任何方法。
注意下面这段代码:
//通过Class对象来直接创建实例
Class clazz1 = Class.forName("tester.Person");
Person p3 = (Person) clazz1.getDeclaredConstructor().newInstance();
p3.setId(3);
p3.setName("Jerry");
System.out.println(p3);
Field id = clazz1.getDeclaredField("id");
id.setAccessible(true);
id.setInt(p3, 10);
System.out.println(p3);
打印的结果是:
Person[id=3, name='Jerry']
Person[id=10, name='Jerry']
看似平平无奇,其实这段代码就破坏了封装性。要知道Person类中的所有域都是私有的,只能通过setter去设置。但是在上一段代码中,我绕过了setter直接将p3的id值改成了10。
3. 谁用到了反射
写过JDBC代码的人应该都会对这段代码记忆犹新:
Class.forName("com.mysql.jdbc.Driver");
这段代码在运行时会去加载MySQL的JDBC驱动,这就是典型的反射的用法。
另外,抽象工厂模式也可以使用反射,利用反射来生成具体的工厂。这段代码逻辑不常见于百度搜索出来结果,请参考《图解设计模式》这本书,如果有机会,我也想写写抽象工厂模式的文章。
网友评论