Java的反射

1. 反射的简单例子

我们平时创建一个对象的时候,总是用这样的方法:

Person person = new Person();

这种方式非常自然,是我们一开始学习Java就在用的一种方式。但是考虑一个场景,看这样一段代码:

List list = new ArrayList<>();
...

过了一段时间以后,我发现使用LinkedList更加靠谱,此时我就会去修改代码:

List list = new LinkedList<>();

如果有一天,我发现另外一种List实现更好,我就要继续修改代码。这种方式实在是太不优雅了,也会让我修改的成本变得很高,因为每次都要重新编译打包。

于是我想到了一个办法,就是搞一个参数:

public List 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 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驱动,这就是典型的反射的用法。

另外,抽象工厂模式也可以使用反射,利用反射来生成具体的工厂。这段代码逻辑不常见于百度搜索出来结果,请参考《图解设计模式》这本书,如果有机会,我也想写写抽象工厂模式的文章。

你可能感兴趣的:(Java的反射)