likes
comments
collection
share

聊一聊反射在Java中的作用

作者站长头像
站长
· 阅读数 34

相信大家对于反射这个名词并不陌生,但如果只是做业务开发可能会很少用到它。最近我在看Spring源码时经常看到一些反射的操作,包括像动态代理的底层也是通过反射来实现的,总的来说反射是很强大的一种能力,这里也记录一些个人对于反射的理解。

在《Java核心技术卷1》中给出的定义是:能够分析类能力的程序叫做反射。乍一听有些抽象,所以我用大白话梳理一下就是,能够在程序运行期间去获取某个类内部的类型信息的方式叫做反射,比如类的属性、方法、构造器等。

想了解反射是如何工作的,就必须要了解在运行时类型信息是如何表示的,这项工作就是由Class对象来完成的

类结构

在Java中,每个对象自从诞生之初就会带一个独有的标识,而在Java运行时会为每个对象都维护一个运行时的类别标识,可以帮助定位到所属哪一个类。

Java中这部分信息保存在对象头中,组织代码的基本结构是类与对象。这两句话是有意在这里提一下,想说的一个点是我们平时使用的类包含了两种对象,即常规对象与Class对象。比如我利用下面这种方式获取了一个User类的Class对象。

User u;
Class clazz = u.getClass();

这个clazz就是Class类型的对象,它描述的维度是类的属性。而clazz就被用来创建类的所有“常规”对象。

举个简单的例子,我定义一个User类,里面有几个属性和一个方法,通过一段测试程序来分别看正常情况下利用对象调其中的方法以及利用反射调其中的方法。

常规方式调用

可以看到,当我们在使用常规方式去创建对象,然后通过对象调方法,这些内容在编译期就可以确定的。

public class User {

    private String name;
    private int age;

    public void speak() {
        System.out.println("hello world");
    }
}

public class ReflectTest {

     public static void main(String[] args)  {
         User user = new User();
         user.speak();
    }
}

// 输出 
hello world

反射方式调用

User类还是用的上面的,在测试类中增加通过反射去动态的生成User对象(达到我们利用Class对象来生成常规对象的目的),然后再调用User类中的speak方法。

测试代码如下

public class ReflectTest {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {

        Class<User> clazz = User.class;
        User user = clazz.newInstance();
        user.speak();
    }
}

可以看到,先是通过User.class的方式来获取了User类的Class对象,然后通过Class对象创建了User对象,继而通过对象调用了speak方法。

代码都很简单,重要的是体会这其中的不同。User类中有几个字段name、age,这时u作为User的对象,描述的是这个User的属性。它就是User的常规对象,这两点是不一样的。

获取Class对象的几种方式

上面通过User.class的方式获取了Class对象的引用,这种方式叫做类字面量。除了这种方式,Java还提供另外几种方式来获取一个Class对象的引用。

还可以用forName的静态方法来获取一个类对象,如

Class clazz = Class.forName("com.cc.User");

这种方式需要传入类的全限定名。

还有我们已知一个对象的时候,直接通过getClass来获取Class对象的引用。

User u;
Class clazz = u.getClass();

我们平时也会见到如Class<?>这样子的用法,实际上呢Class对象实际上表示的是一个类型,这可以是类,也可以不是类,它再隐藏是一点是一个泛型。

分析类能力

反射机制中最强大的一点莫过于分析类的能力。

Field

用于描述类的字段。有一些常用的方法,比如getName,用于返回字段的名称。还有一个getType,用于描述字段类型的一个对象。

getModifier方法用于返回修饰符,这个方法在Method类和Constructor类中也有。在此返回整数的基础上可以调Modifier类中的方法,判断其所属于哪个修饰符。

Method

用于描述类的方法。

Constructor

用于描述类的构造器。

Class

其中也有一些比较常用的方法,如

  • 获取这个类的公共字段 getFields()
  • 获取这个类的公共方法 getMethods()
  • 获取这个类的公共构造方法 getConstructors()

基于以上几个,还提供一些升级的方法,比如

getDeclareFields()、getDeclareMethods()、getDeclareConstructors()**还分别支持类中的私有成员(private修饰)、受保护成员(protected修饰)等信息,但是不包括父类的信息。

访问控制

在Java中有一种机制叫做访问控制,反射的默认行为就是受到了限制,但是可以调用Field、Method等的setAccessible(true)方法来绕过这个控制。

总结

这篇主要总结了反射的一些基本概念以及常用的用法,后面会针对反射进行深入的研究。以及大量真实环境的应用。

参考文献

1.《Java核心技术卷1》 Cay S.Horstmann著

2.《ON JAVA》 BRUCE ECKEL著

转载自:https://juejin.cn/post/7138056126024646687
评论
请登录