前言
反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
Pear pear = new Pear(); //直接初始化,「正射」
pear.setPrice(3);
上面这样子进行类对象的初始化,我们可以理解为「正」。
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用:
Class clazz = Class.forName("com.ducky.reflect.Pear");
Method method = clazz.getMethod("setPrice", int.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Pear),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.ducky.reflect.Pear)。所以说什么是反射?反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
基本数据类型
Class c1 = int.class;
Class c2 = String.class;
// int
c1.getName();
// Java.lang.String
c2.getName();
Class 类的使用
1、万物皆对象
类是 java.lang.Class 类的实例对象。
2、这个对象如何表示
Pear pear = new Pear();
Class c1 = Pear.class;
Class c2 = pear.getClass();
// Class 是类类型
// 不管c1 or c2 都是 Pear 类的类类型,一个类只可能是 Class 类的一个实例对象。
Class c3 = null;
c3 = Class.forName("com.ducky.reflect.Pear");
// 可以通过类类型来创建该类对象实例
// 需有无参构造方法
Pear pear = (Pear) c1.newInstance();
Class 动态加载类
编译时刻加载类是静态加载类。
运行时刻加载类是动态加载类。
// new 创建对象,是静态加载类。
// 在编译时刻就需要加载所有的可能用到的类
Pear pear = new Pear();
// 动态加载类
Class clazz = Class.forName("类的全称");
OA oa = (OA) clazz.newInstance();
Java 反射常用API
获取反射中的 Class 对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:
第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clazz = Class.forName("java.lang.String");
第二种,使用 .class 方法。
这种方法只适合在编译前就知道操作的 Class。
Class clazz = String.class;
第三种,使用类对象的 getClass() 方法。
String str = new String("Hello");
Class clazz = str.getClass();
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。
Class clazz = Pear.class;
Pear pear = (Pear) clz.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class clz = Pear.class;
Constructor constructor = clz.getConstructor();
Pear pear = (Pear) constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
Class clazz = Pear.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Pear pear = (Pear) constructor.newInstance("梨子", 15);
通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class clazz = Pear.class;
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
price
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
Class clazz = Pear.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
name
price
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
打印类的信息
包括类的成员函数,成员变量。
public class ClassUtil {
public static void printClassMessage(Object obj) {
Class c = obj.getClass();
System.out.println("类的名称:"+c.getName());
/*
* 获取成员函数
*/
// 所有public方法
Method[] ms = c.getMethods();
// 该类所有自己声明的方法
// c.getDeclaredMethods()
for (int i = 0;i<ms.length;i++) {
// 得到方法返回值的 类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName() + " ");
// 得到方法名称
System.out.print(ms[i].getName());
System.out.print("(");
// 获取参数类型
// 得到参数列表的类型的类类型。
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1:ParamTypes) {
System.out.print(class1.getName() + ",");
}
System.out.print(")");
}
/*
* 获取成员变量
*/
// 获取所有共有成员变量信息
// Field[] fs = c.getFields();
// 获取该类所有自己声明的成员变量信息
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
Class fieldType = field.getType();
String typeName = fieldtype.getName();
String fieldName = field.getName();
System.out.println(typeName + " " + fieldName);
}
/*
* 获取构造函数
* getConstructors 获取所有公有
* getDeclaredConstructors 得到自己声明的
* (构造方法必须自己声明)
*/
Constructor[] cs = c.getConstructors();
for (Constructor constructor : cs) {
System.out.print(constructor.getName() + "(");
// 获取构造参数的参数列表
// 参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class clazz :paramTypes) {
System.out.print(clazz.getName() + ",");
}
System.out.println(")");
}
}
}
集合泛型
通过 Class, Method 来认识泛型的本质。
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
// ok
list1.add("hello");
// 抛出异常
list1.add(20);
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);
// True
// 也就是说编译之后,两个集合是一样的。
// 即,编译后集合的泛型是去泛型化的。
// 尝试编译后对该集合操作。
Method m = c2.getMethod("add", object.class);
// 绕过编译操作
m.invoke(list1, 20);
System.out.println(list1.size())
System.out.println(list1);
// ["hello", 20]