反射
1、java代码在计算机中的三个阶段:
**× 源代码阶段 ×--------------->× Class 类对象 ×----------------> × Runtime阶段 **
第一阶段:java源代码,java字节码阶段,这个的Java程序还是在硬盘上
第二计算:通过类加载器(ClassLoader)将字节码文件加载到内存,在内存中用Class类对象来表示Java程序,该Class对象主要包含三个对象,描述成员变量的Filed[]、描述构造方法的Constructor[]、描述成员方法的Method[]。注意到他们都是对象数组,这是因为一个类可能有个多个成员变量,多个构造方法,多个成员方法。反射机制就是将类的这几个部分封装为Field、Constructor、Method对象。
2、获得字节码文件对象(class对象)方式
- Class.forName(“全类名”)静态方法,参数是全类名(包名.类名,将字节码文件加载进内存,返回Class对象,多用于配置文件,类名一般定义在配置文件中,读取文件,加载类
- 通过类名的属性Class获取:类名称.class,多用于参数的传递
- 通过对象的方法getClass(),这个方法是在Object类中定义的:对象名.getClass()
注意:其实同一个字节码文件在一次程序的运行过程中只会被加载一次,也就是如果是在同一种程序中,无论使用上面的哪一种方法获取字节码文件对象,都是返回的同一个字节码对象,都是在相同的内存位置。
比如要获得Person类的class对象
Class<Person> personClass = Person.class;
3、Class对象的功能
无论是成员变量,构造方法,方法,都有一个setAccessible(true);调用这个方法,支持暴力反射(忽略权限修饰符的安全检查),使得可以访问获得的私有(private)修饰的方法,成员变量等,否则就会抛出异常
- 获取成员变量们
方法 | 描述 |
---|---|
Fileds[] getFileds() | 返回所有由public修饰的成员变量的对象组成的数组 |
Field getFiled(String name) | 返回指定的由public修饰的成员变量的对象 |
Fields[] getDeclaredFields() | 返回所有成员变量的对象的数组,无论修饰符是什么 |
Field getDeclaredField(String name) | 返回指定的成员变量的对象,无论修饰符是什么 |
package cn.zhuobo.reflect;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Arrays;
public class Demo01 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//
Class<Person> personClass = Person.class;
Field[] fields = personClass.getFields();
System.out.println(Arrays.toString(fields));
// 拿到的其中一个成员变量StringB name的对象
Field name = personClass.getField("name");
Person p = new Person();
// get,获取成员变量的值
Object o = name.get(p);// 传递一个对象,要获得这个对象的成员变量的值
System.out.println(o);// 这样就获取了成员变量的值
//设置成员变量的值
name.set(p, "zhuobo");// 传递两个参数,第一个是要设置成员变量值的对象,第二个是他的值
System.out.println(p);
Field s = personClass.getDeclaredField("s");
s.setAccessible(true);// 调用这个方法,暴力反射(忽略权限修饰符的安全检查),使得可以访问获得的私有(private)成员变量,否则就会抛出异常
Object o1 = s.get(p);
System.out.println(o1);
}
}
- 获取构造方法
方法 | 描述 |
---|---|
Constructor<?>[] getConstructors() | 获取所有的构造方法 |
Constructor |
获取特定的构造方法 |
Constructor<?>[] getDeclaredConstructors() | |
Constructor |
用来获取构造方法,获得构造方法后调用方法newInstance(参数列表)
创建对象,如果是无参数的构造方法,其实也可以使用Class对象的newInstance方法创建对象。
package cn.zhuobo.reflect;
import java.lang.reflect.Constructor;
public class Demo02Constructor {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
// get all costructor
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// get a constructor for given fields
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
System.out.println(constructor);
// use the method newInstance() to creat a object
Person person = constructor.newInstance("猪八戒", 367);
System.out.println(person);
// construct a object without param
Constructor<Person> constructor1 = personClass.getConstructor();
Person person1 = constructor1.newInstance();
System.out.println(person1);
// 创建无参数对象的另一种方法
Person person2 = personClass.newInstance();
System.out.println(person2);
}
}
- 获取成员方法
方法 | 描述 |
---|---|
Method[] getMethods() | 获取所有public修饰的成员方法,还包括继承自父类Object的方法,wait、notify、hashCode等方法 |
Method getMethods(String name, 类<?>...参数类型) | 获取特定的构造方法 |
Method[] getDeclaredMethods() | |
Method getDeclaredMethod(String name, 类<?>...参数类型) |
package cn.zhuobo.reflect;
import java.lang.reflect.Method;
public class Demo03Method {
public static void main(String[] args) throws Exception {
Class<Person> personClass = Person.class;
//获得并调用带参数的方法
Method say = personClass.getMethod("say", String.class);
Person p = new Person("蜘蛛侠",369);
say.invoke(p, "chifanfa");
// 获得并调动空参数的方法
Method say1 = personClass.getMethod("say");
say1.invoke(p);
Method[] methods = personClass.getMethods();
for (Method method : methods) {
//System.out.println(method);
System.out.println(method.getName());// 用来获取方法名的getName
}
}
}
- 获取类名
String getName()
4、反射的一个应用
在还没有知道要使用什么类,使用什么类的什么方法的时候,可以在运行是才决定具体是使用什么了什么方法。看起来好炫的样子,什么都不知道就可以用了。可以利用放射机制可以在某些途径获得要使用的类,要使用的方法。
-
创建一个pro.properties配置文件,文件里面定义要使用的类的名称以及方法(当代码想要使用不同的类,就修改这个配置文件就可以了,不用修改代码)
-
使用全类名的配置文件,这种情况下,代码回调用Student的eat方法
className=cn.zhuobo.reflect.Student methodName=eat
-
这种情况下,调用Person类的say方法
className=cn.zhuobo.reflect.Person methodName=eat
-
-
使用反射的过程
package cn.zhuobo.reflect; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception { // 创建Properties对象 Properties properties = new Properties(); // 获取配置文件,使用类加载器 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("pro.properties"); properties.load(is); // 获取配置文件中定义的数据 String className = properties.getProperty("className"); String methodName = properties.getProperty("methodName"); // 获取对象,创建对象 Class aClass = Class.forName(className); Object o = aClass.newInstance(); //获取方法,调用方法 Method method = aClass.getMethod(methodName); method.invoke(o); } }