一篇文章带你了解Java反射

Posted 8gedouy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇文章带你了解Java反射相关的知识,希望对你有一定的参考价值。

1、Java的封装性

我们都知道Java的一大特性是封装性,即把某个类的行为(方法)和属性封装起来,再对外提供能调用其非私有属性和方法的对象。如果出现了一个东西不仅能调用其私有的方法和属性,还能获取整个类的信息,这样是否破坏了Java的封装这一特性呢?

2、静态语言和动态语言

  • 静态语言:举个例子就是你在写代码声明变量的时候必须指定其数据类型,否则编译无法通过,如C,Java。
  • 动态语言:简单来说就是你在定义变量的时候不用声明其数据类型,在运行程序的时候再检查确定类型,如javascript

可以看到静态语言在编译中就声明类型方便调试,且提高程序运行速度。而动态语言能大大提高了程序的灵活性。那么能不能使Java具备动态语言的特性呢?

可以和你说,反射不仅使Java具备了动态语言的特性,通过反射还能获取整个类的信息和结构,那就带着这两个问题来了解一下反射吧!

Object是所有类的超类,里面定义了一个方法public final Class getClass(),此方法返回了一个Class类型的对象。那这个Class是什么呢?其实Class也是一个类,简单来说Class就是一个描述类的类。每个被JVM加载的类都会有一个Class实例且仅此一个Class实例,通过这个Class的实例能获取到整个类完整的结构。下面提供四个获取Class对象的方法


上面说了获取Class实例的一些方法,那么获取到这个实例后有什么作用呢?来看一下Class类的常用方法,注释中有说明方法的具体作用。


下面的代码演示均使用此实体类


1、获取属性
技术图片
从代码提示可以看到Class对象获取属性有4个方法,无参的返回的是所有属性的数组,需要传参的获取的是指定的属性。此外最重要的区别是含有Declared的方法可以获取私有的属性,必须设置setAccessible(true)才有权限访问私有的属性。对于方法和构造器的获取也是类似的。下面通过一个demo来演示一下获取属性并设置值:


2、获取方法

不同于Field使用get()/set()方法操作属性,Method对方法操作是使用invoke()方法,其返回值是获取到类方法的返回值。传参也是一样,第一个参数为操作的运行时对象,第二个参数对属性来说获取的属性get/set的参数,对方法来说是获取到的类方法需要传的形参列表。


3、获取构造器

上面我们使用clazz.newInstance()来创建对象,但是只适用于无参的构造函数。针对需要传参的构造函数Constructor提供可变长参数的方法getDeclaredConstructor(),所以需获取先到Constructor对象。


4、静态的方法和属性获取

前面的演示都是通过获取到运行时对象对类的属性方法等进行操作,我们都知道如果是静态的方法和属性是可以不用初始化对象直接通过类调用的,那么反射又是怎么操作的呢?看下面代码分析可得,不同于非静态的属性和方法传参必须要运行时对象,对于静态资源的访问我们是不需要的,使用不同的对象获取static修饰的方法和属性都是共享,因此我们在调用静态资源时候习惯会传null。
技术图片

技术图片

  • ClassNotFoundException:加载类找不到该类就会抛出此异常,例如获取Class实例的时候有可能会抛出此异常
  • NoSuchMethodException:如上面的 clazz.getDeclaredMethod(“show”, String.class)在类中不能找到show()方法的时候抛出。类似的NoSuchFieldException异常是对类中找不到指定的属性时候抛出。
  • IllegalAccessException:当应用程序试图反射性创建一个实例,设置或获取一个字段,或调用一个方法,但当前正在执行的方法不具有访问指定类,会抛出IllegalAccessException异常。因此上面我们在访问私有方法和属性的时候都需要设置setAccessible(true) 。
  • InvocationTargetException:当调用invoke()方法获取到的方法的方体内部如果会抛出异常且没有抓获,将由此异常接收处理
  • InstantiationException:试图创建使用一个类的实例使用newInstance()方法时候 ,当指定的类对象不能被实例化,又或者没有空参构造器的时候就会抛出该异常,实例化异常。

以上的异常类都有一个特点,就是都继承了ReflectiveOperationException,该类是通过反射抛出的异常的通用超类。此外我们在使用反射过程中要严格进行异常处理。

上面我们说过反射使Java语言具备动态性,下面我们来了解一下这个动态性。下面定义了一个接口Person,需求是:实现Person接口的类都需要实现invoke()方法。





1、使用以前的方式实现


2、使用反射的方式实现


分析:以前的做法是明确了数据类型使用new创建对象(静态语言的特性),若后面需要新增了teacher,boss…的时候,我们必须要修改源代码继续创建相应的对象才能实现功能,在企业开发中随着业务量的不断增多这样做无疑是繁琐的,代码冗余不好维护需且不断修改源码有风险。但是使用的反射的方式获取全类名通过类加载获取类对象再调用invoke方法,且不需要明确类型也能实现。后面当我们新增teacher,leader等类型的时候我们只需要定义好这个类然后把全类名传递进去className这个参数,这个参数在web项目中能设置从页面传入,又或者从配置文件获取。虽然两种方法都能实现,但是通过反射是不是方便多了?

以上是关于一篇文章带你了解Java反射的主要内容,如果未能解决你的问题,请参考以下文章

Java反射专题笔记总结:一篇文章带你理清反射

一篇文章带你搞懂 Java 注解的原理

java 反射代码片段

一句话木马该怎么实现?现在就带你了解

一篇文章带你了解JavaScript函数

Java——5个Demo带你学懂反射机制中最基础最重要的内容