一、Java反射机制是什么?
Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解编译期和运行期两个概念:
- 编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
- 运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect
包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
如果要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.
Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,返回同一个类型为 Class 的对象。如下:
public class Test02 {
private static Object User;
public static void main(String[] args) throws NoSuchMethodException{
User user = new User("admin", "123312");
Class userClass = user.getClass();// user为 User 类的对象
System.out.println("内部类:获取所有权限为 public 的内部类: "+ Arrays.toString(userClass.getClasses()));
System.out.println("内部类:获取所有内部类: "+ Arrays.toString(userClass.getDeclaredClasses()));
System.out.println("内部类的声明类:如果该类为内部类,则返回它的成员类,否则返回 null"+userClass.getDeclaringClass());
System.out.println("类名称:"+userClass.getName());
System.out.println("继承类:"+userClass.getSuperclass());
System.out.println("实现接口:"+ Arrays.toString(userClass.getInterfaces()));
System.out.println("构造方法:获取所有权限为 public 的方法:"+userClass.getMethod("getName"));
System.out.println("构造方法:获取当前对象的所有方法:"+ Arrays.toString(userClass.getMethods()));
System.out.println("成员变量 获取当前对象的所有方法:"+ Arrays.toString(userClass.getFields()));
System.out.println("成员变量 获取当前对象的所有成员变量:"+ Arrays.toString(userClass.getDeclaredFields()));
}
}
利用 Class 类的对象userClass 可以访问user 对象的描述信息、User 类的信息以及基类 Object 的信息。下面列出了通过反射可以访问的信息。
类型 | 访问方法 | 返回值类型 | 说明 |
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 | |
内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
getDeclaredClasses() | Class 型数组 | 获取所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
优点:
- 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
- 与 Java 动态编译相结合,可以实现无比强大的功能。
- 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
- 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。
二、Java反射机制相关接口
实现 Java 反射机制的类都位于 java.lang.reflect 包中,java.lang.Class 类是 Java 反射机制 API 中的核心类
2.1.java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。Class 类提供了很多方法可以获得运行时对象的相关信息,如下:
public class Test03 {
public static void main(String[] args) {
// 获得Class实例
// 1.通过类型class静态变量
String str = "Hello";
// 2.通过对象的getClass()方法
Class cls2 = str.getClass();
// int 是基本数据类型,所以输出结果为 true;Integer 是类,是引用数据类型,所以输出结果为 false。
// 获得int类型Class实例
Class cls3 = int.class;
// 获得Integer类型Class实例
Class cls4 = Integer.class;
System.out.println("cls2类名称:" + cls2.getName());
System.out.println("cls2是否为接口:" + cls2.isInterface());
System.out.println("cls2是否为数组对象:" + cls2.isArray());
System.out.println("cls2父类名称:" + cls2.getSuperclass().getName());
System.out.println("cls2是否为基本类型:" + cls2.isPrimitive());
System.out.println("cls3是否为基本类型:" + cls3.isPrimitive());
System.out.println("cls4是否为基本类型:" + cls4.isPrimitive());
}
}
结果如下
cls2类名称:java.lang.String
cls2是否为接口:false
cls2是否为数组对象:false
cls2父类名称:java.lang.Object
cls2是否为基本类型:false
cls3是否为基本类型:true
cls4是否为基本类型:false
2.2.java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
- Constructor 类:提供类的构造方法信息。
- Field 类:提供类或接口中成员变量信息。
- Method 类:提供类或接口成员方法信息。
- Array 类:提供了动态创建和访问 Java 数组的方法。
- Modifier 类:提供类和成员访问修饰符信息
public class Test04 {
public static void main(String[] args) {
try {
//动态加载String类的运行时对象
//通过 Class 的静态方法forName(String)创建某个类的运行时对象,其中的参数是类全名字符串,如果在类路径中找不到这个类则抛出 ClassNotFoundException 异常
Class c = Class.forName("java.lang.String");
//获取成员方法集合 通过 Class 的实例方法 getDeclaredMethods() 返回某个类的成员方法对象数组。
Method[] declaredMethods = c.getDeclaredMethods();
//遍历成员方法
for (Method method : declaredMethods) {
System.out.println("-----------------------------------------");
//输出权限修饰符,如public、protected、private
System.out.print(Modifier.toString(method.getModifiers()));
// 输出返回值类型名称
System.out.print(" " + method.getReturnType().getName() + " ");
// 输出方法名称
System.out.println(method.getName() + "();");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.3.获取字节码信息的四种形式
public class Test10 {
public static void main(String[] args) throws ClassNotFoundException {
//案例:以User的字节码信息为案例
//方式1:通过getClass()方法获取
User user = new User();
Class userClass1 = user.getClass();
System.out.println(userClass1);
//方式2:通过内置class属性:
Class userClass2 = User.class;
System.out.println(userClass2);
System.out.println(userClass1 == userClass2);
//注意:方式1和方式2 不常用
//方式3:--》用的最多:调用Class类提供的静态方法forName
Class userClass3 = Class.forName("com.network.program.User");
System.out.println(userClass3);
//方式4:利用类的加载器(了解技能点)
ClassLoader loader = Test.class.getClassLoader();
Class userClass4 = loader.loadClass("com.network.program.User");
System.out.println(userClass4);
}
}
三、获取构造器和创建对象
为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor
类型的对象或者数组。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
访问指定的构造方法,就需要根据该构造方法的入口参数的类型来访问。如下:访问一个参数类型依次为 int 和 String 类型的构造方法,下面的两种方式均可以实现。
objectClass.getDeclaredConstructor(int.class,String.class);
objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});
创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。如下:
方法名称 | 说明 |
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型 |
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型 |
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法 |
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。
静态方法名称 | 说明 | |
isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false | |
isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false | |
isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false | |
isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false | |
isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false | |
toString(int mod) | 以字符串形式返回所有修饰符 |
如下代码判断对象 connect 对应的构造方法是否被 public 修饰,以及以字符串形式获取该构造方法的所有修饰符。
int modifiers = connect.getModifiers(); // 获取构造方法的修饰符整数
boolean isPublic = Modifier.isPublic(modifiers); // 判断修饰符整数是否为public
string allModifiers = Modifier.toString(modifiers);
案例如下:调用 Constructor 类的方法获取构造方法的信息。创建一个User类
public class User implements Serializable{
private String name;
private String password;
public User() {
}
public User(String name) {
this.name = name;
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
创建测试代码,通过反射访问 User类中的所有构造方法,将该构造方法是否带可变类型参数、入口参数类型和可能拋出的异常类型信息输出到控制台。
public class Test05 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取动态类User
Class userClass = User.class;
//获取User类的所有构造方法
Constructor[] declaredConstructors = userClass.getDeclaredConstructors();
//遍历所有构造方法
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("---------------------------------");
//判断构造方法的参数是否可变
System.out.println("查看是否允许带可变数量的参数:"+declaredConstructor.isVarArgs());
System.out.println("构造方法的入口参数类型依次为:");
//获取所有的参数类型:
Class[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType+" ");
}
System.out.println("构造方法可能拋出的异常类型为:");
Class[] exceptionTypes = declaredConstructor.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType+" ");
}
}
//获取指定的构造器:
//得到空构造器
Constructor con1 = userClass.getConstructor();
System.out.println(con1);
//得到两个参数的有参构造器:
Constructor con2 = userClass.getConstructor(String.class, String.class);
System.out.println(con2);
//得到一个参数的有参构造器:并且是private修饰的
Constructor con3 = userClass.getDeclaredConstructor(String.class);
System.out.println(con3);
//有了构造器以后,就可以创建对象:
Object o1 = con1.newInstance();//User{name='null', password='null'}
System.out.println(o1);
Object o2 = con2.newInstance("liming", "123456"); //User{name='liming', password='123456'}
System.out.println(o2);
}
}
四、通过反射获取以及调用方法
要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method
类型的对象或者数组。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>...parameterTypes)
如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。例如,访问一个名称为 max,入口参数类型依次为 int 和 String 类型的方法。
objectClass.getDeclaredConstructor("max",int.class,String.class);
objectClass.getDeclaredConstructor("max",new Class[]{int.class,String.class});
Method 类的常用方法如下:
静态方法名称 | 说明 |
getName() | 获取该方法的名称 |
getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj,Object...args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
获取之前User类中的方法信息
public class Test06 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//获取动态类User
User user = new User("amdin","123321");
Class userClass = user.getClass();
//获取User类的所有构造方法
Method[] declaredMethods = userClass.getDeclaredMethods();
//遍历所有构造方法
for (Method declaredMethod : declaredMethods){
System.out.println("---------------------------------");
System.out.println("方法的名称:"+declaredMethod.getName());
System.out.println("方法是否带有可变数量的参数:"+declaredMethod.isVarArgs());
//获取所有的参数类型:
Class[] parameterTypes = declaredMethod.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType+" ");
}
System.out.println("方法可能拋出的异常类型为:");
Class[] exceptionTypes = declaredMethod.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType+" ");
}
}
System.out.println("----------------通过反射调用方法------------------");
//创建对象
User user1 = new User("admin", "123321");
// 通过反射调用User类的toString方法
//类对象的getMethod方法中,第一个参数为字符串类型,是要调用的实例方法的名称,后续的参数为要调用方法的参数类型的类对象。
Method method2 = userClass.getMethod("toString");
//Method对象的invoke方法,第一个参数为要调用方法所在类的实例对象,后续的参数为要调用的方法的参数。
System.out.println(method2.invoke(user1));
}
}
五、获取属性和对属性进行赋值
通过下列任意一个方法访问成员变量(属性)时将返回 Field 类型的对象或数组。
- getFields()
- getField(String name)
- getDeclaredFields()
- getDeclaredField(String name)
要访问一个名称为 price 的成员变量,示例代码如下:
object.getDeciaredField("price");
方法名称 | 说明 |
getName() | 获得该成员变量的名称 |
getType() | 获取表示该成员变量的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值 |
setlnt(0bject obj, int i) | 将指定对象 obj 中成员变量的值设置为 i |
setFloat(Object obj, float f) | 将指定对象 obj 中成员变量的值设置为 f |
getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值 |
setBoolean(Object obj, boolean b) | 将指定对象 obj 中成员变量的值设置为 b |
getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值 |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
为了演示方便,修改User类如下:
public class User implements Serializable{
private String name;
private String password;
public String sex;
public String address;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(String name, String password, String sex, String address) {
this.name = name;
this.password = password;
this.sex = sex;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
测试类如下:
public class Test08 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//获取运行时类的字节码信息
Class userClass = User.class;
System.out.println("--------------------------------");
//获取属性 getFields:获取运行时类和父类中被public修饰的属性
Field[] fields = userClass.getFields();
System.out.println("获取运行时类和父类中被public修饰的属性:");
for (Field field : fields) {
System.out.println(field);
}
//getDeclaredFields:获取运行时类中的所有属性
System.out.println("--------------------------------");
System.out.println("获取运行时类中的所有属性:");
Field[] declaredFields = userClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//获取指定的属性address
System.out.println("--------------------------------");
System.out.println("获取指定的属性address: ");
Field address = userClass.getField("address");
System.out.println(address);
//某个属性的获取修饰符 1.代表public 2.private
System.out.println("--------------------------------");
System.out.println("获取指定的属性address的修饰符: ");
int modifiers = address.getModifiers();
System.out.println(modifiers);
System.out.println("获取指定的属性name的修饰符: ");
Field name = userClass.getDeclaredField("name");//由于name被private修饰所以用getDeclaredField获取
int modifiers1 = name.getModifiers();
System.out.println(modifiers1);
//获取属性的数据类型:
System.out.println("--------------------------------");
Class clazz = address.getType();
System.out.println(clazz.getName());
//获取属性的名字:
System.out.println("--------------------------------");
System.out.println(address.getName());
System.out.println("--------------------------------");
//给属性赋值,必须有对象
Field address1 = userClass.getField("address");//获取指定的属性address
Field sex = userClass.getField("sex");//获取指定的属性age
Field uname = userClass.getDeclaredField("name");//获取指定的属性name
Field pwd = userClass.getDeclaredField("password");//获取指定的属性password
//通过反射操作类的私有(private)成员变量时,需要通过field.setAccessible(true)将字段设置为可以访问的。
uname.setAccessible(true);
pwd.setAccessible(true);
//创建对象
Object obj = userClass.newInstance();
//设置值
address1.set(obj,"河南省三门峡市区");//给obj这个对象的address属性设置具体的值,"河南省三门峡市区"
sex.set(obj,"男");
uname.set(obj,"liming");
pwd.set(obj,"2342143");
System.out.println(obj);
}
}
六、通过反射获取类的接口,所在包,注解
还是以User类为案例演示如下:
public class Test09 {
public static void main(String[] args) {
//获取字节码信息:
Class cls = User.class;
System.out.println("--------------------------------");
//获取运行时类的接口:
Class[] interfaces = cls.getInterfaces();
for(Class c:interfaces){
System.out.println(c);
}
System.out.println("--------------------------------");
//得到父类的接口:
//先得到父类的字节码信息:
Class superclass = cls.getSuperclass();
//得到接口:
Class[] interfaces1 = superclass.getInterfaces();
for(Class c:interfaces1){
System.out.println(c);
}
System.out.println("--------------------------------");
//获取运行时类所在的包:
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
System.out.println("--------------------------------");
//获取运行类的注解:
Annotation[] annotations = cls.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
}
}