Java高级特性-反射:不写死在代码,还怎么 new 对象?

反射是 Java 的一个高级特性,大量用在各类开源框架上。程序员

在开源框架中,每每以同一套算法,来应对不一样的数据结构。好比,Spring 的依赖注入,咱们不用本身 new 对象了,这工做交给 Spring 去作。算法

然而,咱们要 new 一个对象,就得写在代码上。但 Spring 确定猜不到咱们的类叫什么名字,那 Spring 又是怎么把对象给 new 出来的呢?数据库

这就离不开反射。数据结构

反射的意义与做用

Java 有两种操做类的方式,分别是:非反射、反射。框架

先来讲第一种方式,非反射。this

非反射,就是根据代码,静态地操做类。好比,下面这段代码:code

public class Application {
    public static void main(String[] args) {
        // 建立一个用户对象
        ClientUser client = new ClientUser();
    }
}

这个 main() 方法很简单,就是建立一个用户对象。整个过程是这样的,在 JVM 运行前,你必须先想好要建立哪些对象,而后写在代码上,最后你运行 main() 方法,JVM 给你建立一个用户对象。orm

简单来讲,你写好代码,扔给 JVM 运行,运行完就没了。对象

在这种状况下,程序员必须控制一切,建立什么对象得提早写死在代码上。好比,我要多建立一个商户对象,那就得改代码:get

public class Application {

    public static void main(String[] args) {
        // 建立一个用户对象
        ClientUser client = new ClientUser();
        // 建立一个商户对象
        ShopUser shop = new ShopUser();
        // 省略无数 new 操做
    }
}

若是按照这种作法,只要需求一变,程序员就得改代码,工做效率很低。好比说,你碰上复杂些的项目,不光得建立对象,还得 set 成员变量。这样一来,每新加一个对象,你就得改一堆代码,早晚得累死。

那这些工做能简化吗?

这要用到第二种操做类的方式,反射。反射是一种动态操做类的机制。好比,我要建立一堆对象,那不用提早写死在代码,而是放在配置文件或者数据库上,等到程序运行的时候,再读取配置文件建立对象。

仍是上面的代码,通过反射的改造,就变成这个样子:

public class Application {

    // 模拟配置文件
    private static Set<String> configs = new HashSet<>();
    
    static {
        configs.add("com.jiarupc.reflection.ShopUser");
        configs.add("com.jiarupc.reflection.ClientUser");
        // 省略无数配置
    }
    
    public static void main(String[] args) throws Exception {
        // 读取配置文件
        for (String config : configs) {
            // 经过配置文件,获取类的Class对象
            Class clazz = Class.forName(config);
            // 建立对象
            Object object = clazz.newInstance();
            System.out.println(object);
        }
    }
}

当你运行 main() 方法的时候,程序会先读取配置文件,而后根据配置文件建立对象。用了反射后,你有没有发现,工做变轻松了。一旦新加对象,咱们只要加一行配置文件,不用动其它地方。

看到这儿,你是否是想起某些开源框架?好比,Spring 的依赖注入。

// 加上一行注解,Spring 就接管这个类的建立工做
@Service
public class UserService {
    // 省略业务代码...
}

你在某个类上加一行注解(这至关于加一行配置),Spring 就帮你接管这个类,你不用操心怎么建立对象了。而 Spring 之因此能接管你这个类,是由于利用了 Java 的反射。

简单来讲,咱们若是用好反射,能减小大量重复的代码。

接下来,咱们来看看反射能作什么吧~

反射获取类信息

若是你想操做一个类,那得知道这个类的信息。好比,有哪些变量,有哪些构造器,有哪些方法...没有这些信息,你连代码都无法写,更别谈反射了。

限于篇幅,咱们主要讲怎么获取类的 Class 对象、成员变量、方法。

Class 对象只有 JVM 才能建立,里面有一个类的全部信息,包括:成员变量、方法、构造器等等。

换句话说,建立 Class 对象是 JVM 的事,咱们不用管。但想经过反射来操做一个类,得先拿到这个类的 Class 对象,这有三种方式:

1. Class.forName("类的全限定名")
2. 实例对象.getClass()
3. 类名.class

你能够看下面的代码:

public class User {

    public static void main(String[] args) throws Exception {
        // 1. Class.forName("类的全限定名")
        Class clazz1 = Class.forName("com.jiarupc.reflection.User");

        // 2. 实例对象.getClass()
        User user = new User();
        Class clazz2 = user.getClass();

        // 3. 类名.class
        Class clazz3 = ClientUser.class;
    }
}

当你经过这三种方式,拿到 Class 对象后,就能够用反射获取类的信息了。

Field 对象表明类的成员变量。咱们想拿到一个类的成员变量,能够调用 Class 对象的四个方法。

1. Field getField(String name)          - 获取公共字段
2. Field[] getFields()                  - 获取全部公共字段 
3. Field getDeclaredField(String name)  - 获取成员变量
4. Field[] getDeclaredFields()          - 获取全部成员变量

咱们尝试下获取全部成员变量,代码逻辑是这样的:经过 User 类的全限定名,获取 Class 对象,而后调用 getDeclaredFields() 方法,拿到 User 类的所有成员变量,最后把变量名、类型输出到控制台。

public class User {
    // 惟一标识
    private Long id;
    // 用户名
    private String username;

    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class clazz = Class.forName("com.jiarupc.reflection.User");

        // 获取类的全部成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String msg = String.format("变量名:%s, 类型:%s", field.getName(), field.getType());
            System.out.println(msg);
        }
    }
}

Method 对象表明类的方法。要拿到一个类的方法,Class 对象一样提供了四个方法:

1. Method getMethod(String name, Class[] params)            - 经过方法名、传入参数,获取公共方法
2. Method[] getMethods()                                    - 获取全部公共方法 
3. Method getDeclaredMethod(String name, Class[] params)    - 经过方法名、传入参数,获取任何方法
4. Method[] getDeclaredMethods()                            - 获取全部方法

一样的,咱们尝试下获取全部方法,先经过 User 类的全限定名,获取 Class 对象,而后调用 getDeclaredMethods() 方法,拿到 User 类的所有成员方法,最后把方法名、形参数量输出到控制台。

public class User {
    // 惟一标识
    private Long id;
    // 用户名
    private String username;

    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class clazz = Class.forName("com.jiarupc.reflection.User");

        // 获取类的全部方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            String msg = String.format("方法名:%s, 形参数量:%s", method.getName(), method.getParameterCount());
            System.out.println(msg);
        }
    }
}

看到这儿,你应该能知道:怎么经过反射获取类的信息。

首先,获取类的 Class 对象有三种方式;而后,获取类的成员变量,这对应着 Field 对象;最后,获取类的方法,这对应着 Method 对象。

然而,反射不止能拿到类的信息,还能操做类。

反射操做类

反射能玩出不少花样,但我认为最重要的是:建立对象和调用方法。

建立对象是一切的前提。对反射来讲,若是没有建立对象,那咱们只能看看这个类的信息。好比,有什么成员变量,有什么方法之类的。而若是你想操做一个类,那么第一步就是建立对象。

你想要建立对象,必须调用类的构造器。这分为两种状况,最简单的是:你写了一个类,但没有写构造器,那这个类会自带一个无参的构造器,这就好办了。

public class User {
    // 惟一标识
    private Long id;
    // 用户名
    private String username;

    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class clazz = Class.forName("com.jiarupc.reflection.User");

        // 建立对象
        Object object = clazz.newInstance();
        System.out.println(object);
    }
}

咱们先获取类的 Class 对象,而后调用 newInstance()。

但还有一种状况,我不用 Java 自带的构造器,而是本身写。这种状况会复杂一些,你得指定传入参数的类型,先拿到构造器,再调用 newInstance() 方法。

public class User {
    // 惟一标识
    private Long id;
    // 用户名
    private String username;

    // 构造器1
    public User(Long id) {
        this.id = id;
        this.username = null;
    }
    // 构造器2
    public User(Long id, String username) {
        this.id = id;
        this.username = username;
    }
    
    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class clazz = Class.forName("com.jiarupc.reflection.User");

        // 经过传入参数,获取构造器,再建立对象
        Constructor constructor = clazz.getConstructor(Long.class, String.class);
        Object object = constructor.newInstance(1L, "jiarupc");
        System.out.println(object);
    }
}

咱们要在一开始就设置 id 和 username,那么你得传入参数的类型,先找到构造器2-constructor;而后,传入 id 和 username 到 constructor.newInstance() 方法,就能获得一个用户对象。

当拿到构造器,并建立好对象后,咱们就能够调用对象的方法了。

调用对象的方法分为两步:第一步,找到方法;第二步,调用方法。这听起来是很是简单,事实上也很是简单。你能够看下面的代码。

public class User {
    // 惟一标识
    private Long id;
    // 用户名
    private String username;
    
    // ..忽略 set/get 方法
    
    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class clazz = Class.forName("com.jiarupc.reflection.User");

        // 建立对象
        Object object = clazz.newInstance();
        System.out.println(object);
        
        // 经过方法名、传入参数,找到方法-setUsername
        Method method = clazz.getMethod("setUsername", String.class);
        
        // 调用 object 对象的 setUsername() 方法
        method.invoke(object, "JerryWu");
        
        // 输出全部成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String msg = String.format("变量名:%s, 变量值:%s", field.getName(), field.get(object));
            System.out.println(msg);
        }
    }
}

咱们经过方法名-setUsername、参数类型-String,找到 setUsername 方法;而后,传入参数-username 到 method.invoke(),执行setUsername 方法;最后,输出全部成员变量,验证一下结果。

写在最后

反射是一种动态操做类的机制,它有两个用处。

第一个用处,经过反射,咱们能够拿到一个类的信息,包括:成员变量、方法、构造器等等。

第二个用处,经过反射,咱们能够操做一个类,包括:建立对象、调用对象的方法、修改对象的成员变量。

由于框架要以同一套算法,来应对不一样的数据结构。因此,开源框架大量用到了反射。好比,Spring 的依赖注入就离不开反射。

相关文章
相关标签/搜索