Java中有许多对象在运行时都会出现两种类型:编译时类型和运行时类型。例如下面的语句:
Parent p = new Child();
变量p在编译时的类型为Parent,运行时类型为Child,体现了类的多态。另外,某些情况下程序又需要调用该对象运行时类的方法。为了解决类似的问题,程序需要在运行时发现对象和类的真实结构信息。
在程序运行时获取对象的真实性息有以下两种做法:
- 在知道对象的具体类型的情况下,可以先使用instanceof运算符进行判断,再利用类型强制转换将其转换成运行时的类型变量即可
- 在无法预知该对象属于哪些类的情况下,必须通过反射来发现该对象和类的真实信息
反射(Reflection)机制允许程序在运行时借助Reflection API获取任何类的内部信息,并能直接操作对象的内部属性及方法。反射被视为动态语言的关键。
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时获取任意一个类的所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
- 生成动态代理
反射机制是实现很多流行框架的基础,如Spring,Hibernate等框架都采用反射机制。Reflection API提供了Constructor、Filed和Method类,这三个类定义在java.lang.reflect包中,分别用于描述类的构造方法、属性和方法。下面示例使用反射机制获取类对象的所有方法。
【示例】使用反射机制获取类对象的所有方法
Class c = Class.forName("java.lang.String");
//获取当前类对象的所有方法
Method[] mtds = c.getDeclaredMethods();
for (Method m : mtds)
System.out.println(m);
}
上述代码首先使用Class.forName()方法来获取String类的Class对象;再调用getDeclaredMethods()方法获取String类的所有方法,该方法的返回类型是Method[]方法数组。
Executable抽象类
Executable抽象类代表可执行的类成员。Executable抽象类派生了Constructor和Method两个子类。Executable抽象类提供大量方法来获取参数、修饰符或注解等信息,常用方法如下
方法 | 功能描述 |
---|---|
Parameter[] getParameters() | 获取所有形参,返回一个Parameter[]数组 |
int getParameterCount() | 获取形参个数 |
abstract int getModifiers() | 获取修饰符,返回的整数是修饰符public、protected、private、final、static、abstract等关键字所对应的常量 |
boolean isVarArgs() | 判断是否包含数量可变的形参 |
Constructor类
Constructor类用于表示类的构造方法,通过调用Class对象的getConstructors()方法可以获取当前类的构造方法的集合。Constructor常用方法及使用说明如下
方法 | 功能描述 |
---|---|
String getName() | 返回构造方法的名称 |
Class []getParameterTypes() | 返回当前构造方法的参数类型 |
intgetModifiers() | 返回修饰符的整型标识,返回的整数是修饰符public、protected、private、final、static、abstract等关键字所对应的常量,需使用Modifier工具类的方法解码后才能获得真实的修饰符 |
下述代码调用getConstructors()方法获取指定类的构造方法信息
ConstructorReflectionaDemo.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ConstructorReflectionDemo {
public static void main(String[] args) {
try {
//获取String类对象
Class clazz = Class.forName("java.lang.String");
//返回所有构造方法
Constructor[] ctors = clazz.getDeclaredConstructors();
//遍历构造方法
for(Constructor c : ctors){
//获取构造方法的修饰符
int mod = c.getModifiers();
//使用Modifier工具类的方法获得真实的修饰符,并输出
System.out.print(Modifier.toString(mod));
//获取构造方法的名称,并输出
System.out.print(" " + c.getName() + "(");
//获取构造方法的参数类型
Class[] paramTypes = c.getParameterTypes();
//循环输出结构方法的参数类型
for(int i = 0;i < paramTypes.length;i++){
if(i>0){
System.out.print(",");
}
//输出类型名称
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上述代码通过反射操作,获取了java.lang.String类的所有构造方法及其参数信息。调用getModifiers()方法可以获取构造方法的修饰符,该方法的返回值是整数,即修饰符public、protected、private、final、static、abstract等关键字所对应的常量。此时引入Modifier类,通过调用Modifier.toString(int mod)方法,返回修饰符常量所对应的字符串。反之,可以通过下面语句来查看各修饰符的对应值
System.out.println(Modifier.PUBLIC);
查看java.lang.String类的API,与程序的运行结果对照,运行结果如下
public java.lang.String([B);
public java.lang.String([B,int,int);
public java.lang.String([B,java.nio.charset.Charset);
public java.lang.String([B,java.lang.String);
public java.lang.String([B,int,int,java.nio.charset.Charset);
java.lang.String([C,int,int,java.lang.Void);
java.lang.String(java.lang.AbstractStringBuilder,java.lang.Void);
public java.lang.String(java.lang.StringBuilder);
public java.lang.String(java.lang.StringBuffer);
java.lang.String([B,byte);
public java.lang.String([C,int,int);
public java.lang.String([C);
public java.lang.String(java.lang.String);
public java.lang.String();
public java.lang.String([B,int,int,java.lang.String);
public java.lang.String([B,int);
public java.lang.String([B,int,int,int);
public java.lang.String([I,int,int);
Field类
Field类用于封装属性的信息,调用Class对象的getField()或getField()方法可以获取当前类的所有属性或指定属性。Filed类的常用方法如图
方法 | 功能描述 |
---|---|
String getName() | 返回属性的名称 |
intgetModifiers() | 返回修饰符的整型标识 |
getXxx(Object obj) | 获取属性的值,此处的Xxx对应Java8中的基本类型,如果属性是引用类型,则直接使用get(Object obj)方法 |
setXxx(Object obj,Xxx val) | 设置属性的值,此处的Xxx对应Java8中的基本类型,如果是属性是引用类型,则直接使用get(Object obj,Object val)方法 |
Class[]getType() | 返回当前属性的类型 |
下述代码调用getFileds()方法用于获取指定类的属性信息并显示
FieldDemo.java
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
class Person{
private String name;
private int age;
private String address;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
public class FieldDemo {
public static void main(String[] args) {
try {
//获取Person类对应的Class对象
Class<Person>personClazz = Person.class;
System.out.println("-----Person类的属性-----");
//返回声明的所有属性,包括私有的和受保护的,但不包括超类属性
Field[] fields = personClazz.getDeclaredFields();
for (Field f : fields){
//获取属性的修饰符
int mod = f.getModifiers();
//使用Modifier工具类的方法获得真实的修饰符,并输出
System.out.print(Modifier.toString(mod));
//获取属性的类型,并输出
Class type = f.getType();
System.out.print(" " + type.getName());
//获取属性的名称,并输出
System.out.println(" " + f.getName());
}
//创建一个Person对象
Person p = new Person();
//使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
//获取Person类的name属性对象
Field nameField = personClazz.getDeclaredField("name");
//设置通过反射访问该成员变量时取消访问检查
nameField.setAccessible(true);
//为p对象的name属性设置值,因为String是引用类型,所以直接使用set()方法
nameField.set(p,"张三");
//获取Person类的age属性对象
Field ageField = personClazz.getDeclaredField("age");
//设置通过反射访问该成员变量时取消访问权限检查
ageField.setAccessible(true);
//调用setInt()方法为p对象的age属性设置值
ageField.setInt(p,36);
//获取Person类的address属性对象
Field addressField = personClazz.getDeclaredField("address");
//设置通过反射访问该成员变量时取消访问权限检查
addressField.setAccessible(true);
//为p对象的name属性设置值,因String是引用类型,所以直接使用set()方法
addressField.set(p,"上海");
//输出对象p的信息
System.out.println("-----Person实例-----");
System.out.println(p);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
上述代码定义了一个Person类,该类有name、age和address三个属性。在main()方法中先获取Person类所对应的Class对象,再获取该类的属性并显示,最后分别设置属性值并显示。
注意调用getDeclaredFields()方法可以获取包括私有和受保护的所有属性,但不包括父类的属性;调用getFields()方法可以获取所有public属性,包括从父类继承的公共属性。
运行结果:
-----Person类的属性-----
private java.lang.String name
private int age
private java.lang.String address
-----Person实例-----
Person{name='张三', age=36, address='上海'}
Parameter类
Parameter类也是Java8新增的API,每个Parameter对象代表方法的一个参数。Parameter类中提供了许多方法来获取参数信息,常用方法如下
方法 | 功能描述 |
---|---|
int getModifiers() | 获取参数的修饰符 |
String getName() | 获取参数的形参名 |
Type getParameterizedType() | 获取带泛型的新参类型 |
Class<?> getType() | 获取形参类型 |
boolean isVarArgs() | 判断该参数是否是可变参数 |
boolean isNamePresent() | 判断.class文件中是否包含方法的形参信息 |
使用javac命令编译Java源文件时,默认生成的.class文件不包含方法的形参名信息,因此调用getName()方法不能得到参数的形参名,调用isNamePresent()方法将返回false。如果希望javac命令编译Java源文件时保留形参信息,则需要为编译命令指定-parameters选项
下述代码演示Java8的方法参数反射功能
MethodParameterDemo.java
class MyClass{
public void setName(String name){
}
public void display(String str, List<String> list){
}
}
public class MethodParameterDemo {
public static void main(String[] args) throws Exception{
//获取MyClass的类对象
Class<MyClass>clazz = MyClass.class;
//获取MyClass类的所有public方法
Method[] mtds = clazz.getMethods();
for (Method m : mtds){
//输出方法名
System.out.println("方法名:" + m.getName());
//输出该方法参数个数
System.out.println("参数个数:" + m.getParameterCount());
//获取该方法所有参数
Parameter[] parameters = m.getParameters();
int index = 1;
//遍历所有参数,并输出参数信息
for(Parameter p : parameters){
if (p.isNamePresent()){
System.out.println("---第" + (index++) + "个参数信息---");
System.out.println("参数名:" + p.getName());
System.out.println("形参类型:" + p.getType());
System.out.println("泛型类型:" + p.getParameterizedType());
}
}
System.out.println("--------------------------------------------");
}
}
}
上述代码定义了一个简单的MyClass类用于测试,该类包含两个带参数的方法。在main()方法中,先获取MyClass类的所有方法,并使用嵌套循环来输出该类中的所有方法及参数信息。输出参数信息的前提是p.isNamePresent()为true,即只有当.class文件中包含参数信息时,程序才会执行条件体内输出参数信息的三行代码。因此,需要使用javac-parameters命令编译该程序代码。(就是需要在黑窗口里编译运行)
javac -parameters -d.MethodParameterDemo
再使用java命令运行程序代码:
java test05.MethodParameterDemo