反射机制

动态语言 VS 静态语言

动态语言

动态语言是一类在运行时可以改变其结构的语言

例如:新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除或是其他结构上的变化

通俗点说就是运行时代码可以根据某些条件改变自身结构

主要的动态语言:Object-C、C#、JavaScript、PHP、Python 等

静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言,如:Java、C、C++

Java 不是动态语言,但 Java 可以称之为「准动态语言」,即 Java 有一定的动态性

我们可以利用反射机制获得类似动态语言的特性,Java 的动态性让编程的时候更加灵活!

PS:关于「动态语言」和「动态类型语言」的对比可见 动态语言 vs 动态类型语言

反射概念

反射 (Reflection) 是 Java 被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reelection API 获得任何类的内部信息 (包括:类名、接口、属性、方法等),并能直接操作任意对象的内部属性及方法

例如:一个类有「成员变量、方法、构造方法、包」等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象

加载完类之后,类的类型信息会被存储在方法区中,同时会在堆中生成一个Class类型的对象 (一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息,详情可见 加载阶段

我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射

正常方式 (通过类名获得实例化对象):「引入需要的"包类"名称」 --> 「通过new实例化」 --> 「获得实例化对象」

反射方式 (通过实例化对象获得类名):「实例化对象」 --> 「getClass()方法」 --> 「获得 Class 对象」

Java 反射机制提供的功能

Java 反射的优点和缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响。使用反射基本上是一种解释操作,告诉 JVM 希望做什么,大概比正常new对象慢了十几倍

Java 反射的主要 API

java.lang.Class:代表一个类

java.lang.reflect.Method:代表类的方法

java.lang.reflect.Field:代表类的成员变量

java.lang.reflect.Constructor:代表类的构造器

...

Class 类

Object类中定义了getClass()方法,此方法将被所有子类继承。调用该方法,可以获得该对象的 Class 对象

该方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,如下图所示:

1

实际上所谓的反射从程序运行结果来看也很好理解,即:可以通过对象求出类的名称「全类名」

对象反射后可以得到的信息:某个类的属性、方法、构造器、接口

对每个类而言,jre 都为其保留了一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构「class/interface/enum/annotation/primitive type/void/[]」的有关信息

Class 类的常用方法

方法名功能说明
public static Class<?> forName(String className)返回指定类名的 Class 对象
public T newInstance()调用默认构造函数,返回 Class 对象的一个实例
public String getName()返回此 Class 对象所表示的
实体 (类、接口、数组、void) 的名称
public native Class<? super T> getSuperclass()返回当前 Class 对象父类的 Class 对象
public Class<?>[] getInterfaces()返回当前 Class 对象的接口
public ClassLoader getClassLoader()返回该类的类加载器
public Constructor<?>[] getConstructors()返回一个包含某些 Constructor 对象的数组
public Method getMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象
此对象的形参类型为 parameterType
public Field[] getDeclaredFields()返回 Field 对象的一个数组

获取 Class 类的实例

方法一:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高

方法二:已知某个类的实例,调用该实例的getClass()方法获取 Class 对象

方法三:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法forName()获取,可能抛出ClassNotFoundException异常

方法四:内置基本数据类型可直接用「包装类类名.Type」

方法五:利用 ClassLoader

拥有 Class 对象的类型

创建运行时类的对象

获取运行时类的完整结构

通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation

操作运行时类的内部属性及方法

创建类的对象

调用 Class 对象的newInstance()方法

思考:难道没有无参构造器就不能创建对象吗?

解释:只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,就可以实例化操作

调用指定的方法

通过反射,调用类中的方法,通过 Method 类完成

setAccessible

Method、Field、Constructor 对象都有setAccessible()方法

setAccessible的作用是启动和禁止访问安全检查的开关 (如:一般不能调用 private 属性、方法,但设置为true后,就可以调用)

参数值为true则表示反射的对象在使用时应该取消 Java 语言访问检查

参数为false则表示反射的对象应该实施 Java 语言访问检查

性能对比分析

反射操作泛型

Java 采用泛型擦除的机制来引入泛型,java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题

但是,一旦编译完成,所有和泛型有关的类型全部擦除

为了通过反射操作这些类型,Java 新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一到 Class 类中的类型但又和原始类型齐名的类型

反射操作注解