一、反射的概述
1. 反射的简介
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
2. 反射机制提供的功能
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时获取泛型信息;
- 在运行时调用任意一个对象的成员变量和方法;
- 在运行时处理注解;
- 生成动态代理;
3. 主要API
- java.lang.Class:代表一个类;描述类的一个类;
- java.lang.reflect.Method:代表类的方法;
- java.lang.reflect.Field:代表类的成员变量;
- java.lang.reflect.Constructor:代表类的构造器;
二、Class类
一个Class对象对应的是一个加载到JVM中的一个.class文件;通过Class可以完整地得到一个类中的所有被加载的结构;
1. 类的加载过程
- 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例;
- 换句话说,Class的实例就对应着一个运行时类。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类,并且都是同一个;
2. 获取Class实例的常用方式
【代码示例】
@Test
public void test2() throws Exception {
//方式一:调用运行时类的属性:.class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);//class com.chx.reflectionTest.Person
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class<? extends Person> clazz2 = p1.getClass();
System.out.println(clazz2);//class com.chx.reflectionTest.Person
//方式三:调用Class的静态方法:forName(String classPath)
Class<?> clazz3 = Class.forName("cn.bruce.java.Person");
System.out.println(clazz3);//class com.chx.reflectionTest.Person
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz1 == clazz3);//true
还有一种使用类加载器:ClassLoader;
3. Class实例可以代表的结构
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;
(2)interface:接口;
(3)[]:数组; 只要数组的元素类型与维度一样,就是同一个Class;
(4)enum:枚举;
(5)annotation:注解@interface;
(6)primitive type:基本数据类型;
(7)void;
三、反射的简单使用
1. 获取运行时类的对象
【代码示例】
先获取一个运行时类对应的Class对象(上述多种方式),在通过该对象创建运行时类的对象;这里的运行时类就是 Person;
Class<Person> clazz = Person.class;
Person person1 = clazz.newInstance();
【注意】
调用newInstance()方法,会创建对应的运行时类的对象。内部调用了运行时类的空参的构造器;
要想此方法正常的创建运行时类的对象,要求:
- 运行时类必须提供空参的构造器;
- 空参的构造器的访问权限得够。通常,设置为public;
【说明】
在javabean中要求提供一个public的空参构造器。原因:
- 便于通过反射,创建运行时类的对象;
- 便于子类继承此运行时类时,默认调用super()时,保证父类此构造器;
2. 获取运行时类的完整结构
我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等;
【代码示例】
以获取属性为例
Class<Person> clazz = Person.class;
Person person1 = clazz.newInstance();
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性;
Field[] fields = clazz.getFields();
//getDeclaredFields():获取当前运行类中声明的所有属性。(不包含父类声明的属性)
Field[] declaredFields =clazz.getDeclaredFields();
其余的结构获取都是类似的;
例如获取方法 :getMethods(); getDeclaredMethods(); 返回值则是 Method[] 数组;还能获取方法中的修饰符等等;
【获取泛型示例】
//获取运行时类的带泛型的父类的泛型;generic就是泛型的意思
@Test
public void test4(){
//获取Class对象;
Class<Person> clazz = Person.class;
//获取父类(带泛型);
Type genericSuperclass = clazz.getGenericSuperclass();
//向下转型为ParameterizedType类型;
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型参数;getActualTypeArguments();
Type[] actualTypeArguments = paramType.getActualTypeArguments();
}
3. 调用运行时类的指定结构
与上述不同,上述都是获取全部的结构,如全部的属性;现在我们要获取指定的结构;
3.1 调用指定的属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作;
-
getDeclaredField(String fieldName)获取运行时类中指定变量名的属性;返回Field类型;
-
调用Field的方法:
- set():参数1;指明设置哪个对象的属性 ;参数2:将此属性值设置为多少;
- get():参数1:获取哪个对象的当前属性值;
Class<Person> clazz = Person.class;
//创建运行时类的对象;
Person person = clazz.newInstance();
//1、getDeclaredField(String fieldName)获取运行时类中指定变量名的属性;
Field name = clazz.getDeclaredField("name");
//2、保证当前属性是可访问的;私有也能访问;
name.setAccessible(true);
//3、设置、获取当前属性的值;
name.set(person, "张三");
System.out.println(name.get(person));
3.2 调用指定的方法
通过getDeclaredMethod获取Method实例,通过Method实例调用运行时类的方法;
【代码示例】
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person person = clazz.newInstance();
/*
1、获取指定的某个方法
getDeclaredMethod()
参数1:指明获取的方法的名称;
参数2:指明获取的方法的形参列表;
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2、保证当前方法是可访问的;
show.setAccessible(true);
/*
3、调用方法的invoke():
参数1:方法的调用者;参数2:给方法形参赋值的实参;
invoke()的返回值即为对应类中调用的方法的返回值;
*/
Object returnValue = show.invoke(person, "China");
//等同于:String nation = person.show("China");
System.out.println(returnValue);
System.out.println("静态方法示例:");
//在Person类中添加了private static void showDesc()
//获取方法;
Method showDesc = clazz.getDeclaredMethod("showDesc");
//强制访问;私有也能访问;
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null;
//传入Person.class参数调用静态方法;
Object returnVal = showDesc.invoke(Person.class);
//不需要运行时类也能访问静态方法;
Object returnVal = showDesc.invoke(null);
System.out.println(returnVal);//null
3.3 调用指定的构造器
【代码示例】
@Test
public void test4() throws Exception{
Class<Person> clazz = Person.class;
//1.获取指定的构造器,指明构造器的参数列表
Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
declaredConstructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person person = declaredConstructor.newInstance("张三");
System.out.println(person);
}