Java反射特性提供了在运行时可以动态访问和修改类和实例内部的状态的功能。反射是Java语言里面一个高级的话题之一,使用反射我们可以在运行时轻松的内省一个类,接口以及枚举,可以获取他们的结构,方法和字段信息,即使在编译期间没法访问。最后我们也可以通过反射来实例化一个对象,调用对象的方法和修改字段的值等。
反射其实是一个非常强大的概念,虽然平时在正常的开发功能中,我们几乎很少用到,但作为Java SE里面核心的骨干特性,反射在大型框架里面非常常见,举例如下:
(1)JUnit :使用反射来解析所有带有@Test注解的方法,然后动态调用这些方法。
(2)Spring :依赖注入,包括AOP底层也需要使用反射配合动态代理才能完成切面功能。
(3)Tomcat:web容器通过反射解析web.xml文件里面的url,来正确的转发请求
(4)Eclipse 和 Intellj IDEA:方法名的自动补全功能
(5)Struts 注解或者xml配置的action请求转发
(6)Hibernate 动态Bean与数据库表的映射
实际上使用反射的框架还有很多很多,现在思考一个问题,这些框架使用反射的原因是什么?其实最主要的原因在于所有的这些框架,在运行之前是不知道我们用户自己定义的类,接口,以及各种方法和字段的,而通过反射则可以在运行时动态加载这些类,所以极大的提升了架构的灵活性。
但事物有利必有弊,反射也不是银弹,它的缺点也很显而易见:
(1)性能低。动态的加载和调用,需要额外的花费时间去解析和调用,所以性能相比正常的new要低很多。
(2)安全问题。反射会破坏封装的特性,因为它可以访问private修饰的字段和方法,所以是不安全的。
(3)维护成本高。由于反射的代码在编译期间是不会产生的任何效果的,所以对于理解和调试不太方便,只有等到运行时才能反馈效果。
这也是我们正常的编程中很少使用反射的原因,但在一些架构高度灵活的框架中,反射其实是必不可少的道具,所以我们应该权衡考虑,做到不滥用,不误用。
在Java里面有基本类型和引用类型两种类型,所有的类,接口,数组是引用类型,继承自父类Object类。基本类型就是boolean,byte,short,int,long,char,float,double这8种。
java.lang.Class类是完成反射的入口基础类,它提供了在运行时访问对象属性和创建对象,调用方法,字段赋值等有用的API。
为了演示方便和全面,我们创建了如下的类和接口用来测试:
1,BaseClass
2, BaseInterface
3,BaseImpl extends BaseClass implements BaseInterface
一,获取Class对象本身
我们先来复习下Java里面获取一个实例的Class的三种方式:
(1)Class.forName()
(2) 实例的getClass()
(3) 非实例的.class 或者包装类的TYPE字段
下面来看一个使用示例:
输出:
二,获取父类或者超类
输出如下:
三,获取所有公共成员的类
getClasses()方法可以获取所有使用public修饰的类,包括类本身,父类或者接口里面声明的类
输出
三,获取类本身所有包含的其他类集合
getDeclaredClasses()可以获取类本身声明的不管任何权限修饰的类成员,不包含超类里面定义的类:
输出:
四,获取包名
五,获取修饰符
六,获取类本身泛型参数的声明
输出
注意这里不能获取类真正的泛型的类型,只能获取声明
七,获取所有的公共方法
获取所有的public修饰的方法,包含父类的
获取该类本身所有的public修饰的方法
八,获取所有的构造方法
九,获取所有的字段
十,获取所有的注解
使用反射可以获取类里面的public和private等修饰的字段,并能够访问和修改其值,下面我们通过一个例子来看一下:
get/set public 字段:
get/set private 字段:
这里以Java的HashMap作为例子,并调用了其公共的put方法:
然后我们看下,如何调用某个类的私有方法:
调用无参数的公共构造器:
调用有参数的私有构造器:
此外,使用反射还可以获取方法的注解和数组字段的实例的声明,这里就不细说了,感兴趣的朋友可以自己研究下。
本文主要介绍了Java里面反射是什么,以及它的应用场景和优缺点,最后结合实例给出了常见的反射调用的API例子等,除了本文中描述的反射相关的功能外,反射还可以配合动态代理来实现AOP功能或者配合类加载器来实现应用程序的加载和热加载的功能,这些功能都是比较高级的特性,在特定的场景下可以发挥很大的作用,从而使得我们应用程序更加灵活和具有扩展性。
相关文章:
深入理解Java类加载器机制
理解Java里面的代理模式