一、什么是反射机制
java的反射(Reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类所具有的成员变量和方法,可以调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象方法的功能称为java语言的反射机制。反射被视为动态语言的关键。
反射机制所需要的类主要有java.lang包中的Class类和java.lang.reflect包中的Constructor类、Field类、Methods类和Parameter类。
二、Class类
Class类是一个比较特殊的类,它是反射机制的基础,Class类的对象表示正在运行的Java程序中的类或接口,即任何一个类被加载时,Java将类的.class文件读入内存的同时,会为之自动创建一个java.lang.Class对象代表该.class文件,通过这个Class对象以获取类的许多基本信息,这就是反射机制。所以想了解反射机制,首先需要认识Class类。
Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法创建的,因此不能显式地创建一个Class对象。
每个类被加载之后,系统都会为该类生成一个对应的Class对象,通过Class对象就可以访问到JVM中该类的信息,一旦类被加载到JVM中,同一个类将不会被再次载入,被载入JVM中的类都有一个唯一标识就是该类的全名(包名+类名)。在Java中 程序获得Class对象有如下3种方式:
方式 | 示例 |
---|---|
1.使用Class类的静态方法forName(String className),其中参数className标识所需类的全名。 注意:forName()方法声明抛出ClassNotFoundException异常,因此使用该方法时必须捕获或抛出该异常。 | jdbc中加载数据库驱动类:Class.forName("com.mysql.cj.jdbc.Driver"); |
2.用类名调用该类的class属性来获得该类对应的Class对象,即“类名.class”。 | Class<People> pe= People.class; 将返回People类所对应的Class对象赋给pe变量。 |
3.用对象调用getClass()方法来获得类对应的Class对象,即“对象.getClass()”。该方法是Object类中的一个方法,因此所有对象调用该方法都可以返回所属类对应的Class对象。 | People pe= new People(); Class cls = pe.getClass(); |
通过类的class属性获得该类所对应的Class对象,会使代码更安全,程序性能更好。因此大部分情况下建议使用第二种方式。但如果只获得一个字符串,例如获得String类所对应的Class对象,则不能使用String.class方式,而是使用Class.forName("java.lang.String").注意:如果要想获得基本数据类型的class对象,可以使用对应的打包类加上.TYPE,例如:Integer.TYPE可以获得int的class对象,但要想获得Integer.class的Class对象,则必须使用Integer.class.在获得Class对象后,就可以获得Class对象的基本信息。
下表列出了Class类的一些常用方法:
常用方法 | 功能说明 | 示例 |
---|---|---|
public Package getPackage() | 返回Class对象所对应类的存放路径 | |
public static Class<?> forName(String className) | 返回名称为className的类或接口的Class对象 | |
public String getName() | 返回Class对象所对应勒的“包、类名”形式的全名 | |
public Class<? super T> getSuperclass() | 返回Class对象所对应类的父类的Class对象 | |
public Class<?>[] getInterfaces() | 返回Class对象所对应类所实现的所有接口 | |
public Annotation[] getAnnotations() | 以数组的形式返回该程序元素上的所有注解 | |
public Constructor<T> getConstructor(Class<?>...parameterTypes) | 返回Class对象所对应类的指定参数列表参数类型的public构造方法 | Constructor constructor = pe.getConstructor(String.class,int.class); //返回参数类型为String和int的构造方法 |
public Constructor<?>[] getConstructors() | 返回Class对象所对应类的所有public构造方法 | |
public Constructor<T> getDeclaredConstructor(Class<?>...pararameterTypes) | 返回Class对象所对应类的指定参数列表的构造方法,与访问权限无关 | |
public Constructor<?>[] getDeclaredConstructors() | 返回Class对象所对应类的所有构造方法,与访问权限无关 | |
public Field getField(String name) | 返回Class对象所对应类的名为name的public成员变量 | |
public Field[] getFields() | 返回Class对象所对应类的所有public成员变量 | |
public Field[] getDeclaredFields() | 返回Class对象所对应类的所有成员变量,与访问权限无关 | 获取所有成员变量: Field[] fields = pe.getDeclaredFields(); |
public Method getMethod(String name,Class<?>...parameterTypes) | 返回Class对象所对应的指定参数列表的public方法 | |
public Method[] getMethods() | 返回Class对象所对应类的所有public成员方法 | 获取所有成员方法: Method[] methods = pe.getMethods(); |
public Method[] getDeclaredMethods() | 返回Class对象所对应类的所有成员方法,与访问权限无关 |
说明:通过getFields()和getMethods()方法火的权限为public成员变量和成员方法时,还包括从父类继承得到的成员变量的成员方法;而通过getDeclaredFields()和getDeclaredMethods()方法只是获得在本类中定义的所有成员变量和成员方法。
三、java.lang.reflect包中的常用类
1、Executable抽象类
java.lang.reflect.Executable类提供了大量方法用来获取参数、修饰符或注解等信息,其常用方法如下表所示。
常用方法 | 功能说明 | 示例 |
---|---|---|
public Parameter[] getParameters() | 返回所有形参,存入数组Parameter[]中 | 获取某方法的所有参数: Parameter[] parameters = method.getParameters(); |
public int getParameterCount() | 返回形参的个数 | 获取某方法的参数个数: int count = method.getParameterCount(); |
public abstract Class<?>[] getParameterTypes() | 按声明顺序以Class数组的形式返回个参数的类型 | 获取构造方法的参数类型: Class[] pts = constructor.getParameterTypes(); 输出构造方法参数的类型: for(int i=0;i<pts.length;i++ ){ System.out.print("参数:"+pts[i].getName()); } |
public abstract int getModifiers() | 返回整数表示的修饰符public、protected、private、final、static、abstract等关键字所对应的常量 | 获取修饰符: int mod = pe.getModifiers(); Modifier.toString(int mod); |
public boolean isVarArgs() | 判断是否包含数量可变的参数 |
注意:getModifiers()方法返回的是以整数表示的修饰符。此时引入Modifier类,通过调用Modifier.toString(int mod)方法返回修饰符常量所对应的字符串。
2、Constructor
java.lang.reflect.Constructor类是java.lang.reflect.Executable类的直接子类,用于表示类的构造方法。通过Class对象的getConstructors()方法可以获得当前运行时类的构造方法。其常用方法如下表所示:
常用方法 | 功能说明 |
---|---|
public String getName() | 返回构造方法的名字 |
public T newInstance(Object...initargs) | 通过该构造方法利用指定参数列表创建一个该类的对象,如果未设置参数则表示采用默认无参的构造方法 |
public void setAccessible(boolean flag) | 如果该构造方法的权限为private,默认不允许通过反射利用newInstance()方法创建对象,如果先执行该方法,并将入口参数设置为true,则允许创建 |
3、Method
java.lang.reflect.Method类是java.lang.reflect.Executable类的直接子类,用于封装成员方法的信息,调用Class对象的getMethod()方法或getMethods()方法可以获得当前运行时类的指定方法或所有方法。其常用方法如下表所示:
常用方法 | 功能说明 | 示例 |
---|---|---|
public String getName() | 返回方法的名称 | 输出方法名: method.getName(); |
public Class<?> getReturnType() | 以Class对象的形式返回当前方法的返回值类型 | |
public Object invoke(Object obj,Object...args) | 利用给定参数列表执行指定对象obj中的该方法 |
4、Field
java.lang.reflect.Field类用于封装成员变量信息,调用Class对象的getField()方法或getFields()可以获得当前运行时类的指定成员变量或所有成员变量。其常用方法如下表所示:
常用方法 | 功能说明 | 示例 |
---|---|---|
public String getName() | 返回成员变量的名称 | field.getName(); |
Xxx getXxx() | 返回成员变量的值,其中Xxx代表基本类型,如果成员变量是引用类型,则直接使用get(Object obj)方法 | |
void setXxx(Object obj,Xxx val) | 设置成员变量的值,其中Xxx代表基本类型,如果成员变量是引用类型,则直接使用set(Object obj,Object val)方法 | |
public Class<?> getType() | 返回当前成员变量的类型 | 获取成员变量的类型: Class type = field.getType(); 输出成员变量的类型: type.getName(); |
5、Parameter
java.lang.reflect.Parameter类是参数类,每个Parameter对象代表方法的一个参数。java.lang.reflect.Parameter类中提供了许多方法来获取参数信息,其常用方法如下表所示:
常用方法 | 功能说明 | 示例 |
---|---|---|
public int getModifiers() | 返回参数的修饰符 | |
public String getName() | 返回参数的形参名 | 输出参数的名称: parameter.getName(); |
public Type getParameterizedType() | 返回带泛型的形参类型 | |
public Class<?> getType() | 返回形参类型 | 输出参数的类型: parameter.getType(); |
public boolean isVarArgs() | 判断该参数是否为可变参数 | |
public boolean isNamePresent() | 判断.class文件中是否包含方法的形参名信息 | 要想输出参数信息,前提是isNamePresent()方法的返回值为true |
注意: 使用javac命令编译java源文件时,默认生成class字节码文件中不包含方法的形参名信息。因此调用getName()方法不能得到参数的形参名,调用isNamePresent()方法将返回false。如果希望javac命令编译Java源文件时保留形参信息,则需要为javac命令指定parameters选项。
四、反射机制的意义
1.反射机制极大地提高了程序的 灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
2.通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
3.使用反射机制能够在运行时构造一个勒的对象,判断一个类所具有的成员变量和方法,调用一个对象的方法。
4.反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
正式由于以上特征,所以反射能动态编译和创建对象,极大地激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而收到编程界的青睐。
尽管反射机制带来了极大地灵活性和方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:
1.性能问题:
Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。
2.安全限制:
使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
3.程序健壮性:
反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
注:
本文参考了百度百科-验证