目录
一、什么是反射
Java 反射(Reflection)是指 Java 程序在运行时,可以动态的加载、探知、使用编译期间完全未知的类。也就是说,Java 程序可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法等。这种在运行时动态获取类的信息以及动态调用对象方法的功能称为 Java 反射机制
例如在使用 Eclipse 时,当开发者定义了一个类Car
,里面写了一些方法,再创建Car
类对象car
并输入car.
时,Eclipse 会弹出car
对象可用的方法给程序员选择,这些都是反射机制最常见的例子
二、Class 类
Class
类是使用 Java 反射机制的入口,封装了一个类或接口的运行时信息,开发者通过调用 Class
类的方法可以获取这些信息。例如可以通过 Class
中的 getDeclaredMethods()
方法获取类的所有方法
下面通过一个案例来演示如何通过以上几种方式获取Class
类对象
import java.lang.reflect.*;
public class TestClass{
public static void main(String[] args) {
//如果将被建模的类类型未知,用Class<?>表示
Class<?> c1 = null;
Class<?> c2 = null;
Class<?> c3 = null;
Class<?> c4 = null;
Class<?> c5 = null;
try{
//建议采用这种形式
c1 = Class.forName("java.lang.Object");
}catch(Exception e){
e.printStackTrace();
}
c2 = new TestClass().getClass();
c3 = TestClass.class;
String name = new String("大力士");
c4 = name.getClass();
c5 = name.getClass().getSuperclass();
System.out.println("Class.forName(\"java.lang.Object\") 类名称:" + c1.getName());
System.out.println("new TestClass().getClass() 类名称:" + c2.getName());
System.out.println("TestClass.class 类名称:" + c3.getName());
System.out.println("String name = \"大力士\"");
System.out.println("name.getClass() 类名称:" + c4.getName());
System.out.println("name.getClass().getSuperclass() 类名称:" + c5.getName());
}
}
运行结果:
三、Class
类的一些常用方法
-
Field[] getFields()
返回一个包含 Field 对象的数组,存放该类或接口的所有符合访问修饰符要求的公共属性(含继承而来的属性)。
-
Field[] getDeclaredFields()
返回一个包含 Field 对象的数组,存放该类或接口中 private 等四种访问修饰符修饰的所有属性(不含继承而来的属性)。可见,该方法可以突破访问修饰符的限制。
-
Method[] getMethods()
返回一个包含 Method 对象的数组,数组中存放的是该类及父类(或父接口)中用 public 修饰的方法。
-
Method[] getDeclaredMethods()
返回一个包含 Method 对象的数组,存放该类(或接口)中 private 等四种访问修饰符修饰的所有方法(不含父类或父接口中定义的方法)。可见,该方法也可以突破访问修饰符的限制。
-
Constructor[] getConstructors()
返回一个包含 Constructor 对象的数组,存放该类中所有用 public 修饰的公共构造方法。
-
Constructor getConstructor(Class[] args)
返回一个指定参数列表的 Constructor 对象。
-
Class[] getInterfaces()
返回一个包含 Class 对象的数组,存放该类或接口实现的接口。
-
T newInstance()
使用无参构造方法创建该类的一个新实例。
-
String getName()
以 String 的形式返回该类(类、接口、数组类、基本类型或 void)的完整名。
1、获取方法信息
通过 Class
类的 getMethods()
方法、getDeclaredMethods()
方法、getMethod(String name, Class[] args)
方法和 getDeclaredMethod(String name, Class[] args)
等方法,程序员可以获得对应类的特定方法组或方法,返回值为 Method 对象数组或 Method 对象。
Class
类的 getDeclaredMethods()
方法获得了 Class
所表示的要获取的类的 private
等全部修饰符修饰的方法,但不包括继承而来的方法。使用 Class 类的 getMethods()
方法获取的是 Class 所表示的要获取的类及其父类中所有的公共方法(即 public 修饰的方法)。这就是 getDeclaredMethods()
和 getMethods()
方法的主要区别。
2、获取属性信息
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class TestField {
public static void main(String args[]) {
try {
Class c = Class.forName("Sub");
Scanner input = new Scanner(System.in);
System.out.print("请输入你想获取Sub类的哪个属性的类型:");
String name = input.next();
//通过指定属性名获取属性对象
Field sf = c.getDeclaredField(name);
//得到属性类型
System.out.println("Sub类" + name + "属性的类型为:" + sf.getType());
System.out.println("****************************************");
//返回Field对象数组,存放该类或接口的所有属性(不包含父类或父接口中的方法)
Field flist[] = c.getDeclaredFields();
System.out.println("Sub类getDeclaredFields()得到的属性如下:");
//遍历所有属性
for (int i = 0; i < flist.length; i++) {
System.out.println("****************************************");
Field f = flist[i];
System.out.println("属性" + (i + 1) + "名称为:" + f.getName()); //得到属性名
System.out.println("该属性所在的类或接口为:" + f.getDeclaringClass());
System.out.println("该属性的类型为:" + f.getType()); //得到属性类型
//以整数形式返回由此Field对象表示的属性的Java访问权限修饰符
int m = f.getModifiers();
//使用Modifier类对表示访问权限修饰符的整数进行解码显示
System.out.println("该属性的修饰符为:" + Modifier.toString(m));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、获取构造方法信息
通过 Class 类,可以获得属性和方法,但是 getDeclaredMethods()
和 getMethods()
等获取方法的方法只能获得普通方法,不能获得类的构造方法。接下来,通过 Class
类的 getConstructors()
方法和 getDeclaredConstructors()
方法,可以获得对应类的构造方法,返回值为 Constructor 对象数组
四、动态调用
通过 Class
类的方法获取了对应类的属性、方法和构造方法的详细信息,但反射的意义远不止如此。接下来,将通过之前获取的属性、方法和构造方法的详细信息,来动态创建对象、修改属性和调用方法。
1、创建对象
可使用 newInstance()
方法和使用 newInstance(Object[] args)
方法两种方式实例化对象
到目前为止,可以看出点反射机制的作用—可以根据用户运行时输入的信息,动态创建不同的对象,再调用对象的方法执行相关的功能
通过 Class 类的 newInstance()
方法创建对象,该方法要求该 Class 对应类有无参构造方法。执行 newInstance()
方法实际上就是使用对应类的无参构造方法来创建该类的实例,其代码的作用等价于Super sup = new Super();
如果要想使用有参构造方法创建对象,则需要先通过 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance(Object[] args)
方法来创建该 Class 对象对应类的实例
需要注意的是,通过 Class 对象获得指定 Constructor 对象的方法 getDeclaredConstructor((Class[] args))
中,参数列表为 Class 类数组。通过 Constructor 的 newInstance()
方法,也可以创建无参对象,只要在调用 getDeclaredConstructor((Class[] args))
和 newInstance(Object[] args)
方法时,参数列表为空即可。
2、修改属性
import java.lang.reflect.*;
public class TestChangeField{
public static void main(String args[]) {
try {
Class c = Class.forName("Super2");
Super2 sup = (Super2)c.newInstance();
//通过属性名获得Field对象
Field f = c.getDeclaredField("supPri");//supPri为私有属性
//取消属性的访问权限控制,即使private属性也可以进行访问
f.setAccessible(true);
//调用get(Object o)方法取得对象o对应属性值
System.out.println("取消访问权限控制后访问supPri,其值为:" + f.get(sup));
//调用set(Object o,Object v)方法设置对象o对应属性值
f.set(sup, 20);
System.out.println("f.set(sup, 20)后访问supPri,其值为:" + f.get(sup));
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、调用方法
使用反射机制,通过调用 Method 类的一些方法,动态执行 Class 对应类的方法
假设现在有这样的需求,需要在程序运行时,根据用户提供的不同参数列表(方法名称、参数个数、参数类型),动态调用不同的方法完成不同的功能。
import java.lang.reflect.*;
public class TestInvokeMethod {
public int add(int x, int y) {
return x + y;
}
public int add(int x) {
return x + 1;
}
public int multiply(int x, int y) {
return x * y;
}
public int multiply(int x) {
return x * x;
}
public static void main(String args[]) {
try {
Class c = TestInvokeMethod.class;
Object obj = c.newInstance();
//通过方法名、参数类型列表,获得Method对象
Method m = c.getDeclaredMethod("multiply", new Class[]{int.class, int.class});
//invoke(Object o,Object[] args)方法调用对象o对应方法
System.out.println("调用方法:multiply,输入值为int型3和4,结果为:"
+ m.invoke(obj, new Object[]{3, 4}));
Method m2 = c.getDeclaredMethod("add", new Class[]{int.class});
System.out.println("调用方法:add,输入值为int型18,结果为:"
+ m2.invoke(obj, new Object[]{18}));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、操作动态数组
Java 在创建数组的时候,需要指定数组长度,且数组长度不可变。而 java.lang.reflect
包下提供了一个 Array
类,这个类中包括一系列 static
方法,通过这些方法可以创建动态数组,对数组元素进行赋值、取值操作
Array 类提供的主要方法(均为静态方法)如下。
-
Object newInstance(Class componentType, int length)
创建一个元素类型为 componentType、长度为 length 的新数组。
-
Object newInstance(Class componentType, int... dimensions)
创建一个元素类型为 componentType、长度为 length、维度为 dimensions 的多维数组。
-
void setXxx(Object array, int index,xxx val)
将数组对象 array 中索引元素的值设置为指定的 xxx 类型的 val 值。
-
xxx getXxx(Object array, int index)
获取数组对象 array 中索引为 index 的数组元素值。
五、总结
- Java 反射是指 Java 程序在运行时,可以动态的加载类,并调用类的属性和方法;
- 使用反射可以获取类的属性、方法、构造方法、父类、父接口等所有信息,并且可以动态的调用属性、方法;
- 反射的入口类是 Class,常用的获取 Class 对象的方法有 Class.forName("全类名")**、 **类名.class和 对象名.getClass() 三种;
getMethods()
只能获取 public 修饰的方法,但这些方法既可以是本类中定义的、也可以在父类(或父接口)中定义的;getDeclaredMethods()
可以获取 private 等四种访问修饰符修饰的方法,但这些方法只能是在本类定义的,不包含父类(或父接口)中定义的方法。getDeclaredFields()
与getFields()
,及getDeclaredConstructors()
和getConstructors()
方法的区别与之类似;- 在使用反射突破访问修饰符限制时,需要先对方法或属性设置
setAccessible(true)
; - Array 类提供了一系列 static 方法,可以创建动态数组,对数组元素进行赋值、取值操作。例如,可以使用
Array.newInstance()
方法来创建数组对象。 - 反射大量存在于后续学习的框架底层,但在初级阶段应用的比较少。