反射机制功能:
- 实现泛型数组,来动态操作Java代码;
- 运行时分析类的能力;
- 运行时检查对象;
Class类
程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。该标识跟踪每个对象所属的类。JVM利用运行时类型信息,选择要执行的正确方法。
获取名字
public static void main(String[] args) {
var e = new Employee("Fucker", 1, 1, 1, 1);
var cl = e.getClass();
println(cl); //~class reflection.Employee
// 静态获取类的名字
println(cl.getName()); //~ reflection.Employee
// 动态获取类的名字
String className = cl.getName();
try {
// 获得类名对应的Class对象
Class t = Class.forName(className);
println(t);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
// T是任意地Java类型,T.class将代表匹配的类对象。
Class cl1 = Random.class;
Class cl2 = int.class; // int不是类,然而int.class是一个类型的对象。
Class cl3 = Double[].class;
}
构造实例
JVM为每一个类型(Class)管理一个唯一的Class对象。因此可以用‘==’符号比较
if (e.getClass() == Employee.getClass()) ...
调用getConstructor()得到一个Constructor类型对象,然后使用newInstance方法构造实例。
var className2 = "reflection.Temp";
try {
var cl2 = Class.forName(className2);
Object obj = cl2.getConstructor().newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
声明异常
异常有两种类型:非检查型和检查型
非检查型:例如数组越界错误或访问null引用;
检查型:编译器会检查你是否知道这个异常并做好准备来处理后果。
一种检查型异常的处理手段
public static void doSth(String name) throws ReflectiveOperationException{
var cl = Class.forName(name);
cl.getConstructor().newInstance();
}
调用该方法的任何一种方法也需要该声明
利用反射分析类的能力
使用Field、Method和Constructor
这三个类都在reflect包中,拥有各种可以分析类的方法。
/**
* 两个方法都返回包含Field对象的数组,前者里面每个对象对应这个类或其父类的公共字段;后者对应
* 这个类的全部字段。但如果类中没有字段,或者Class对象描述的是基本类型或数组类型,则返回长度
* 为0的数组
*/
Field[] getFields();
Field[] getDeclaredFields();
/**
* 返回包含Method对象的数组,前者返回所有公共方法,包括从父类继承来的公共方法;后者返回全部
* 方法,但不包括由父类继承来的方法。
*/
Method[] getMethod();
Method[] getDeclaredMethod();
/**
* 返回包含Constructor对象的数组,包含该类的所有公共构造器或全部构造器
*/
Constructor[] getConstructor();
Constructor[] getDeclaredConstructor();
/**
* 通用代码,以构造器为例:
* 简单来说,即用forName()获取对应的class对象,然后使用方法获得其信息
*/
public static void printConstructors(Class cl) {
// 获取这个类所有的构造器,包括public,private,protected
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String name = c.getName();
// 打印修饰符,toString负责打印,getModifiers负责返回一个描述修饰符的整数
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) print(modifiers + " ");
print(name + "(");
// 打印构造器的参数
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) print(", ");
print(paramTypes[j].getName());
}
println(");");
}
}
使用反射在运行时分析对象
利用反射机制可以查看在编译时还不知道的对象字段。利用Field的get和set方法:
反射机制的默认行为受限于Java的访问权限。所以get和set受限于字段的访问权限
但是可以用三大类的对象的setAccessible方法覆盖Java的访问控制:
psvm() {
Temp tp = new Temp("fuck you!");
var cl3 = tp.getClass();
// get
var f = cl3.getDeclaredField("t");
Object v = f.get(tp);
println(v);
// set
f.set(tp, "ok");
Object vc = f.get(tp);
println(vc);
// 假设Employee的name字段为private
var harry = new Employee("Fucker" , 1, 1,1, 1);
var cl1 = harry.getClass();
var f2 = cl1.getDeclaredField("name");
// 设置可访问标识
f2.setAccessible(true);
Object vs = f2.get(harry);
println(vs);
}
class Temp {
public String t;
public Temp(String t) {
this.t = t;
pln("bitch");
}
}
可以利用toString()方法,获得字段并设置它们为可访问的。查看src/reflection/ObjectAnalyzer.java
要清楚java.util.ArrayList != Array类型
public static void main(String[] args) {
var a = new ArrayList<Integer>();
Class<?> cl = a.getClass();
var b = new String[10];
Class<?> cl2 = b.getClass();
System.out.println(cl); //~class java.util.ArrayList
System.out.println(cl2); //~class [Ljava.lang.String;
System.out.println(cl.isArray()); //~false
System.out.println(cl2.isArray()); //~true
}
使用反射编写泛型数组代码
清楚Array类的常用方法:
public class CopyOfTest {
public static void main(String[] args) {
int[] a = {1, 2, 3};
// 复制后强转
a = (int[]) copyOf(a, 10);
System.out.println(Arrays.toString(a));
String[] b = {"Fuck", "Your", "Ass"};
b = (String[]) copyOf(b, 10);
System.out.println(Arrays.toString(b));
Employee[] c = {new Employee("Shit", 1, 1, 1, 1),
new Employee("Pussy", 1, 1, 1, 1),
new Manager("Anal", 2, 2, 2, 2)};
c = (Employee[]) copyOf(c, 10);
System.out.println(Arrays.toString(c));
}
/**
* 这个方法要声明返回类型、参入参数类型为Object,不能为Object[],因为Object[]无法向下强转
* 为其他数组!
* System.arraycopy()参数查看源码!
* @param a 原数组
* @param newLength 新数组长度
* @return 复制后的新数组
*/
public static Object copyOf(Object a, int newLength) {
// 获取原数组的Class类型实例
Class<?> cl = a.getClass();
if (!cl.isArray()) return null;
// 获取原数组元素的Class类型
Class<?> componentType = cl.getComponentType();
int length = Array.getLength(a);
// 创建一个有确定的原数组元素Class对象类型和数组长度的新数组,第二个参数可以设为多维
Object newArray = Array.newInstance(componentType, length);
// 开始复制数组,将原数组元素复制到新数组。
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
}
/**
* 一些常用Array的方法,其中Xxx是基本类型
*/
// 返回存储在给定数组中给定索引位置上的值
static xxx getXxx(Object array, int index)
// 将一个新值存储到给定数组中国呢的给定位置上
static xxx setXxx(Object array, int index)
// 返回一个有给定类型,给定大小的新数组;可以指定多维
static Object newInstance(Class componentType, int[] length)
调用任意方法和构造器
使用invoke方法调用
public class MethodTable {
public static void main(String[] args)
throws ReflectiveOperationException{
// 获取Method对象
Method square = MethodTable.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}
/**
* 平方, 供Method.invoke用
* @param x 一个double
* @return 还是double
*/
public static double square(double x) { return x * x;}
/**
* 打印表格
* @param from 表格开始一行的数
* @param to 表格结尾一行的数
* @param n 行数
* @param f 方法
*/
public static void printTable(double from, double to, int n, Method f)
throws ReflectiveOperationException {
System.out.println(f);
// x的公差
double dx = (to - from) / (n - 1);
// %n代表行分隔符。invoke()要传入符合f接受的参数
for (double x = from; x <= to; x += dx) {
double y = (Double) f.invoke(null, x);
System.out.printf("%10.4f |%10.4f%n", x, y);
}
}
}
然而不建议使用invoke。最好用内部类以及lambda表达式。