一、反射的定义
反射是Java被视为动态语言的关键。
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
二、三种方式获取Class对象
- 通过类名获取 类名.class
Class clazz = Person.class;
- 通过对象获取 对象名.getClass()
Person p = new Pserson();
Class clazz = p.getClass();
- 通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
String className = "com.test.refle.Person";
Class clazz = Class.forName(className);
三、反射基本操作
1.获取构造信息
//获取所有构造方法
Constructor<Person>[] constructors = (Constructor<Person>[]) clazz.getConstructors();
//获取单个指定参数的构造方法
Constructor<Person> constructor = clazz.getConstructor(String.class, int.class);
//使用构造器创建对象
Person p= constructor.newInstance("fanshao", 21);
2.获取方法信息
//获取所有公共和父类方法
Method[] methods = clazz.getMethods();
//获取所有方法,包括私有方法
methods = clazz.getDeclaredMethods();
//获取指定方法
Method method = clazz.getDeclaredMethod("setName", String.class);
//反射调用方法
Object p = clazz.newInstance();
method.invoke(p ,"fanshao");
//反射调用私有方法
method = clazz.getDeclaredMethod("setRealName", String.class);
method.setAccessible(true);
method.invoke(p, "fanshao-real");
3.获取字段信息
//获取所有字段,包括私有字段
Field[] fields = clazz.getDeclaredFields();
//获取指定字段
Field field = clazz.getDeclaredField("name");
//获取字段的值
Person p = new Person("fnashao",21);
Object val = field.get(person);
//设置字段的值
field.setAccessible(true);
field.set(person,"fanshao2");
4.通过反射创建数组
Array.newInstance(String.class, 12);
5.通过反射获取泛型信息
1.TypeVariable
泛型类型变量,封装泛型上下限等信息。
public class TypeVariableTest<T extends Cloneable & Closeable, K> {
T t;
K k;
public static void main(String[] args) {
try {
// 获取字段的类型
Field field1 = TypeVariableTest.class.getDeclaredField("t");
Field field2 = TypeVariableTest.class.getDeclaredField("k");
TypeVariable typeT = (TypeVariable) field1.getGenericType();
TypeVariable typeK = (TypeVariable) field2.getGenericType();
//获取字段名称
System.out.print(typeT.getName() + "\n"); // T
System.out.print(typeK.getName() + "\n"); // K
// 获取泛型上界
/*泛型T的上界:interface java.lang.Cloneable
泛型T的上界:interface java.io.Closeable*/
for (Type type : typeT.getBounds()) {
System.out.print("泛型T的上界:" + type + "\n");
}
/*泛型K的上界:class java.lang.Object 默认上界为Object*/
for (Type type : typeK.getBounds()) {
System.out.print("泛型K的上界:" + type + "\n");
}
} catch (Exception e) {
System.out.print(e.getMessage());
}
}
}
2.ParameterizedType
具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)。
public class ParameterizedTypeTest {
Map<String, Integer> map;
List<Integer>[] list;
public static void main(String[] args) {
try{
Field f = ParameterizedTypeTest.class.getDeclaredField("map");
// java.util.Map<java.lang.String, java.lang.Integer>
System.out.println(f.getGenericType());
ParameterizedType pType = (ParameterizedType) f.getGenericType();
//interface java.util.Map
System.out.println(pType.getRawType());
//class java.lang.String
//class java.lang.Integer
for (Type type : pType.getActualTypeArguments()) {
System.out.println(type);
}
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
如果是数组,通过GenericArrayType获取
Field f = ParameterizedTypeTest .class.getDeclaredField("list");
GenericArrayType genericType = (GenericArrayType) f.getGenericType();
四、反射的原理
1.类加载机制
要理解反射的原理,需要先搞清楚类加载机制。
Java文件需要经历编译和运行两个过程。
编译就是通过javac命令将.java文件编译成字节码即.class文件;
运行则是把编译生成的.class文件交给Java虚拟机(JVM)执行。
类加载过程就是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
另外JVM是按需加载,某个类在用到的时候才会加载,且只会加载一次。
类的加载机制有三个阶段:加载、链接、初始化;三个阶段交叉进行。
链接又有三个过程:验证、准备、解析。
2.Java的反射
Java的反射和类加载过程是类似的。
Java的反射就是利用上面第一步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。
3.Java的反射和ClassLoader对比
Class.forName 和 ClassLoader 都可以用来装载类,如前面说的包含加载、链接、初始化等操作,但是它们装载类的方式是有区别,简单来说:ClassLoader的loadClass不会执行类的初始化代码。
Class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
如果有静态变量,变量的赋值执行了静态方法,那么Class.forName()也会执行静态方法。
并且静态代码块的执行顺序先于静态方法。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。