背景:Java程序中的所有对一项都有两种类型:编译时类型和运行时类型(由于多态导致的),这可能会导致对象的编译时类型和运行时类型不一致。
反射(Reflection)是被是为动态语言的关键,反射机制允许程序再运行起家借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,再堆内的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们称之为——反射:
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时获取泛型信息;
- 在运行时调用任意一个对象的成员变量和方法;
- 在运行时处理注解;
- 生成动态代理
反射相关的主要API:
- java.lang.Class:代表一个类;
- java.lang.reflect.Method:代表类的方法;
- java.lang.reflect.Fild:代表类的成员变量;
- java.lang.reflect.Constructor:代表类的构造器
……
反射的优缺点:
- 优点
- 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力;
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类。
- 缺点
- 反射的性能较低(反射机制主要应用在对灵活性和扩展性要求很高的系统框架上);
- 反射会模糊程序内部逻辑,可读性较差。
面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不适用反射,也可以使用反射,这两者的区别:
- 不使用反射时,我们考虑封装性。比如出了类之后,就不能调用该类中的私有结构;
- 使用反射,我们可以调用运行时类中(运行java代码过程中,加载到内存方法区的类)任意的构造器、属性、方法,包括了私有的属性、方法、构造器。
以前创建对象并调用方法的方式,与通过反射创建对象调用方法的方式对比的话,使用场景:
- 从程序员开发者的角度来说,开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,使用非反射的方法多一些;
- 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以在设计框架的时候,会大量的使用反射。意味着,需要学习框架的源码,那么就需要学习反射。(框架 = 注解+ 反射+ 设计模式)
通过反射,可以调用类中的私有结构,是否与面向对象的封装性是否有冲突?
- 封装性:体现的是是否建议我们调用内部api的问题,比如private声明的结构,意味着不建议调用;
- 反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所以我们有能力进行调用。
对Class类的理解
Class是反射的源头。
获取Class实例的几种方式:
- 调用运行时类的静态属性:class。如: Class 变量名 = 类名.class;
- 调用运行时类的对象的getClass()。如:Class 变量名 = 实例.getClass();
- 调用Class的静态方法forName(String className)。如:Class 变量名 = Class.forName(“全类名”);
- 使用类的加载器(了解,与前一种方式的使用场景相同)
哪些类型有Class对象: - class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;
- interface:接口;
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
简而言之,所有Java类。
类的加载过程:
类加载器(JDK8版本为例):
类加载器分类: - BootstrapClassLoader:引导类加载器、启动类加载器
- 继承于ClassLoader的类加载器
- ExtensionClassLoader:扩展类加载器;
- SystenClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器;
- 用户自定义类的加载器