目录
程序运行时,允许改变程序结构或变量类型的语言称为动态语言。从这个观点看,Python、Ruby、JavaScript是动态语言,而C、C++、Java不是动态语言。尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:反射(Reflection)。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。反射的一个重要应用就是解析注解,Spring框架中的控制反转就是通过Java的反射机制来实现的。
1. 什么是反射
Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。简单来说,只要给定类的名字,就可以通过反射机制来获得类的所有信息。反射是Java被视为动态(或者准动态)语言的一个关键性质。
Java 反射机制主要提供了以下功能:
(1) 在运行时判断任意一个对象所属的类。
(2) 在运行时构造任意一个类的对象。
(3) 在运行时判断任意一个类所具有的成员变量和方法。
(4) 在运行时调用任意一个对象的方法。
(5) 生成动态代理。
实现Java反射机制的类位于包java.lang.reflect中,主要包括:Class、Constructor、Method、Filed等。
Class:代表一个类,是反射的起源,用于获取与该类相关的各种信息。
Constructor:代表某个类中的一个构造方法。
Method:代表某个类中的一个成员方法。
Fileld:代表某个类中的一个成员变量。
2. Class类
Class类是实现Java反射机制的核心类,这个类是所有.class文件的抽象体现。也就是说,所有类的字节码文件,都可以看做是Class类的实例化对象。在Java中,每一个类通过编译后会生成一个.class文件的字节码文件,而同一个类实例化出来的所有对象,都会对应于同样一个.class文件。也就是说,在Java中,一个类无论实例化多少对象,这些对象都会对应同一个Class对象。图1描述了类、字节码文件、Class类和类实例之间的关系。
图1. 类、字节码文件、Class类与类实例关系
在Java中有一个Object类,它是所有Java类的继承根源,Object类内部有一个方法:getClass(),其返回一个Class对象。Class类和一般类一样继承自Object类,其对象用以表示Java程序运行时的类和接口,也用来表达枚举、数组、基本数据类型以及关键词void。Class没有公共构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass()方法自动构造的,因此不能显示地声明一个Class对象。通过Class对象可以获得其所表示的类型的所有信息,表1列出了Class类的常用方法。
方法 | 功能描述 |
public static Class<?> forName(String className) | 该方法返回给定串名相应的Class对象。 |
public String getName() | 返回Class对象表示的类型的完整路径名称 |
public Class getSuperclass() | 返回Class对象表示的类的父类对象 |
public Constructor getConstructor(Class... parametTypes) | 返回Class对象表示的类的指定参数列表的公有构造方法 |
public Constructor[] getConstructors() | 返回Class对象表示的类的所有公有构造方法 |
public Constructor getDeclaredConstructor(class... parameterTypes) | 返回Class对象表示的类的指定参数列表的构造方法,与访问权限无关 |
public Constructor[] getDeclaredConstructors() | 返回Class对象表示的类的所有构造方法,与访问权限无关 |
public Field getField(String name) | 返回Class对象表示的类或接口中指定名称的公有成员变量 |
public Field[] getFields() | 返回Class对象表示的类或接口中所有公有成员变量 |
public Field getDeclaredField(String name) | 返回Class对象表示的类或接口中指定名称的成员变量,与访问权限无关 |
public Field[] getDeclaredFields() | 返回Class对象表示的类或接口中所有成员变量,与访问权限无关 |
public Method getMethod(String name, class... parameterTypes) | 返回Class对象表示的类或接口中指名称和定参数列表的公有成员方法 |
public Method[] getMethods() | 返回Class对象表示的类或接口中的所有公有成员方法,包括从父类或父接口中继承的方法 |
public Method[] getDeclaredMethod(String name, class¼ parameterTypes) | 返回Class对象表示的类或接口中指定名称和参数列表的成员方法,与权限无关 |
public Method[] getDeclaredMethods() | 返回Class对象表示的类或接口中所有成员方法,与权限无关 |
public Class[] getInterfaces() | 返回Class对象表示的类所实现的所有接口 |
Class类是反射的起源,要想操纵类中的属性和方法,都必须从获取Class对象开始。获取Class对象主要有三种方式:
方式一:使用类Class的静态方法forName(String className)获得与字符串className对应的Class对象,其中,className是类的全名。
例如,
Class c1=Class.forName("java.lang.String"); //获取String类的Class对象
Class c2=Class.forName("my.student.domain.Student");//获得Student类的Class对象
另外,forName()方法声明抛出ClassNotFoundException异常,因此调用该方法时必须捕获或抛出该异常。
方式二:使用一个对象的getClass()方法获得该对象所属的类的Class对象。
例如,
String str="Zhangsan"; //str为一个String类型的对象
Class c3=str.getClass(); //通过str的getClass()方法获取String类的Class对象
Student stu=new Student(); //stu为Student类的一个对象
Class c4=stu.getClass(); //通过stu的getClass()方法Student类获取Class对象
方式三:通过类的class属性获得Class对象。
例如,
Class c5=String.class; //获取String类的Class对象
Class c6=Student.class;//获取Student类的Class对象
Class c7=Integer.TYPE; //获取基本类型int的Class对象
Class c8=Integer.class; //获取Integer类的Class对象
如果想要获得基本数据类型的Class对象,可以使用其所对应的类的TYPE属性,例如,Integer.TYPE可以获得int的Class对象,但是要想获得Integer类的Class对象,必须使用Integer.class。
除了上面介绍的Class类之外,与反射相关的常用类还有Constructor、Method、Field和Parameter,其中,类Constructor和Method是类Executable的子类。Executable是一个抽象类,该类对象代表可执行类成员。Executable类提供了大量的方法用来获取参数、修饰符或注解等信息。
3. Constructor类
Constructor类代表某个类中的一个构造方法,通过Class对象的getConstructors()方法可以获得当前运行时类的所有构造方法,每个构造方法对应一个Constructor对象,通过调用Constructor对象的newInstance()方法可以在不使用new运算符的情况下动态地创建对象。Constructor类所包含的主要方法如表2所示。
方法 | 功能描述 |
public T newInstance(Object... initatgs) | 调用Constructor对象表示的构造方法创建对象,如果未设置参数则表示采用默认无参数的构造方法 |
public void setAccessible(Boolean flag) | 如果一个类的构造方法的权限为private,则默认不允许通过反射创建该类的对象,如果先执行该方法,并将入口参数设置为true,则允许创建对象 |
public String getName() | 返回构造方法的名称 |
【例1】下面程序演示了Constructor对象的获取和动态创建对象。
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
Class userClass=User.class;//获取User类的对象Class对象
//通过Class对象的默认构造方法创建对象
Constructor userCons=userClass.getConstructor(new Class[]{});
Object obj1=userCons.newInstance(new Object[]{});
//通过Class对象的带参数的构造方法创建对象
userCons=userClass.getConstructor(new Class[]{int.class, String.class});
Object obj2=userCons.newInstance(new Object[]{1,"fanchao"});
//将Object对象转换为User对象,然后执行print()方法
((User)obj1).print();
((User)obj2).print();
}
}
class User{
int id;
String name;
//默认构造方法
public User() {}
//带参数构造方法
public User(int id,String name) {
this.id=id;
this.name=name;
}
public void print() {
System.out.println("id="+id+",name="+name);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
执行类ConstructorDemo,程序运行结果为:
id=0,name=null
id=1,name=fanchao
4. Method类
Method类用于封装成员方法的信息,调用Class对象的getMethod()方法或getMethods()方法可以获得当前运行时类的指定方法或所有方法,每个方法对应一个Method对象,通过调用Method对象的invoke()方法可以在不使用“.”运算符的情况下,动态地执行对象的相应方法。Method类所包含的主要方法如表3所示。
方法 | 功能描述 |
public Class getDeclaringClass() | 得到一个Class对象 |
public String getName() | 获得Method类对象所表示的方法名 |
public Class[] getParameterTypes() | 获得Method类对象表示的方法中的参数类型 |
public Object invoke(Object o, Object… args) | 调用Method类对象表示的方法,相当于对象o用参数args调用该方法 |
【例2】下面程序演示了Method对象的获取和动态执行相应方法。
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class userClass=User.class;//获取User类的对象Class对象
//获取User类的默认构造方法创建对象
Constructor userCons=userClass.getConstructor(new Class[]{});
User user=(User)userCons.newInstance(new Object[]{});
//获取User类的setName()方法,该方法包含一个参数
Method setNameMethod=userClass.getMethod("setName", new Class[] {String.class});
//执行user对象的setName()方法,实参为"Fanchao",
setNameMethod.invoke(user, new Object[] {new String("Fanchao")});
//获取User类的getName()方法,该方法无参数,返回值为String类型
Method getNameMethod=userClass.getMethod("getName", new Class[] {});
//执行user对象getName()方法,返回值为一个Object对象,将其转换为String字符串
Object nameObj=getNameMethod.invoke(user, new Object[] {});
String name=(String)nameObj;
System.out.println("name="+name);
}
}
程序执行结果为:
name=Fanchao
5. Field类
Field类用于封装成员变量的信息,调用Class对象的getFiled()方法或getFileds()方法可获得当前运行时类的指定成员变量或所有成员变量,每个成员变量对象一个Field对象,通过Field对象的get()方法和set()方法可以为动态地获取和设置对象的成员变量值。Field类所包含的主要方法如表4所示。
方法 | 功能描述 |
public String getName() | 获得Filed类对象所表示的变量的名称 |
public Class<?> getType() | 获得Filed类对象所表示的变量的类型 |
public Object get(Object obj) | 获得Filed类对象所表示的变量的值,其中,obj为该变量所属的对象 |
public void set(Object obj, Object value) | 设置Filed类对象所表示的变量的值为value,其中,obj为该变量所属的对象 |
public Xxx getXxx(Object obj) | 获得Filed类对象所表示的变量的值,其中,Xxx代表基本类型,obj为该变量所属的对象 |
public void setXxx(Object obj, Xxx value) | 设置Filed类对象所表示的变量的值为value,其中,Xxx代表基本类型,obj为该变量所属的对象 |
【例3】下面程序演示了Field对象的获取以及利用Filed对象动态地设置成员变量值。
public class FieldDemo {
public static void main(String[] args) throws Exception {
Class userClass=User.class;//获取User类的对象Class对象
//获取User类的默认构造方法创建对象
Constructor userCons=userClass.getConstructor(new Class[]{});
User user=(User)userCons.newInstance(new Object[]{});
//获取User类的成员变量id
Field idField=userClass.getDeclaredField("id");
//给对象user的成员变量id赋值1
idField.set(user, 1);
//获取User类的成员变量name
Field nameField=userClass.getDeclaredField("name");
//给对象user的成员变量name赋值"Fanchao"
nameField.set(user, "Fanchao");
//输出对象user的成员变量id和name的值
System.out.println(idField.getName()+"="+idField.getInt(user));
System.out.println(nameField.getName()+"="+(String)nameField.get(user));
}
}
程序运行结果为:
id=1
name=Fanchao
6. Parameter类
Parameter类用于封装方法的参数信息,每个Parameter对象代表方法的一个参数。Parameter类所包含的主要方法如表5所示。
方法 | 功能描述 |
public String getName() | 获得Parameter类对象所表示的参数的名称 |
public Class getType() | 获得Parameter类对象所表示的参数的类型 |
public Type getParameterizedType() | 获得Parameter类对象所表示的带泛型参数的类型 |
public boolean isVarArgs() | 判断Parameter类对象所表示的参数是否为可变参数 |
public boolean isNamePresent() | 判断.class文件中是否包含方法的参数名信息 |
使用javac.exe命令编译Java源文件时,默认生成的.class字节码文件中不包含方法的形参名信息,因此,调用Parameter对象isNamePresent()方法的返回值为false,调用Parameter对象的getName()方法不能得到我们自己定义的参数名,而是返回argX形式的名称(X是参数的序号,从0开始)。如果希望使用javac.exe命令编译Java源文件时保留形参信息,则需要为编译命令指定-parameters选项。
【例4】下面程序演示了利用反射返回一个类的所有公有方法(包括从父类继承的公有方法)及其参数信息。
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterDemo {
public static void main(String[] args) {
Class userClass=User.class;//获取User类的对象Class对象
//获取User类的所有公有方法
Method[] methods=userClass.getMethods();
System.out.println("类"+userClass.getName()+"包含的公有方法:");
for(Method method:methods) {
System.out.println("方法名:"+method.getName()
+", 返回类型:"+method.getReturnType());
Parameter[] parameters=method.getParameters();
//检索每个公有方法的每个参数
for(Parameter parameter:parameters) {
System.out.println("\t参数名:"+parameter.getName()
+", 参数类型:"+parameter.getType());
}
System.out.println("--------------------------------");
}
}
}
使用带参数-patameters的javac命令编译上述源文件,程序运行结果为:
类charpter7.User包含的公有方法:
方法名:getName, 返回类型:class java.lang.String
--------------------------------
方法名:setName, 返回类型:void
参数名:name, 参数类型:class java.lang.String
--------------------------------
方法名:getId, 返回类型:int
--------------------------------
方法名:print, 返回类型:void
--------------------------------
方法名:setId, 返回类型:void
参数名:id, 参数类型:int
--------------------------------
方法名:wait, 返回类型:void
参数名:arg0, 参数类型:long
参数名:arg1, 参数类型:int
--------------------------------
方法名:wait, 返回类型:void
--------------------------------
方法名:wait, 返回类型:void
参数名:arg0, 参数类型:long
-------------------------------
方法名:equals, 返回类型:boolean
参数名:arg0, 参数类型:class java.lang.Object
--------------------------------
方法名:toString, 返回类型:class java.lang.String
--------------------------------
方法名:hashCode, 返回类型:int
--------------------------------
方法名:getClass, 返回类型:class java.lang.Class
--------------------------------
方法名:notify, 返回类型:void
--------------------------------
方法名:notifyAll, 返回类型:void
--------------------------------
输出结果中的中方法wait()、equals()、toString()、hashCode()、getClass()、notify()和notifyAll()是从默认的父类Object中继承而来的。因为Object类的.class字节码文件默认是不包含参数信息的,所以没有输出这些方法的参数信息。