欢迎浏览我的博客 获得更多精彩文章
https://boyn.top
反射
1.1 反射简介
在Java中,反射是一个十分有趣的特性.所谓反射,就是在我们运行时,可以分析一个类和对象.
最常见的反射的应用,莫过于是Spring了,在Spring中,大量运用到了JavaBean和反射机制
在Core Java中,他是这样来介绍反射的:
反射库( Reflection library) 提供了一个非常丰富且精心设计的工具集, 以便编写能够动
态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans 中, 它是 Java组件的体系结构
(有关 JavaBeans 的详细内容在卷 II 中阐述)。使用反射, Java 可以支持 Visual Basic 用户习惯
使用的工具。特别是在设计或运行中添加新类时, 能够快速地应用开发工具动态地查询新添
加类的能力。
我们在深入学习反射之前,需要先了解他的作用是什么:
- 在运行时分析类的能力。
- 在运行时查看对象, 例如, 编写一个 toString() 方法供所有类使用。
- 实现通用的数组操作代码
1.2 Class类
在Java程序运行时,JVM会创建一个运行时标识(Runtime Signal)来跟踪每个对象的类,我们可以根据这个,来获取类的信息
首先来看一个实例
public class Person {
private String name;
public String getName() {
return name;
}
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Person p1 = new Person("Jack");
System.out.println(p.getClass().getName());
//Output:Reflection.Person
}
}
在这里,定义了一个简单的Person类,并在main方法中输出了他的类名.
我们把目光放到getClass()方法中,它是Object类定义的一个本地方法,其实现细节对用户隐藏,那么我们也就不深究,它返回一个Class对象,其中尖括号表示它是一个泛型类.
public final native Class<?> getClass();
Class类定义在了java.lang包中,从JDK1.0就已经存在了,是一个非常古老的类,它代表的是在java程序运行时的类和接口的代表类,值得注意的是,虽然Java的原始类型(boolean, byte, char, short, int, long, float, and double)不是Object的子类,但是他们也被表示为Class对象,其原因我认为是为了暴露原始类型的数据,以及在不同的类中对这些原始类型的引用.
鉴于上面所说的,如果当我们知道一个类的类型,那么我们是否可以通过Class类来构造一个跟这个类相同的实例呢?当然是可以的!请看下面
try {
Person p = new Person("Jack");
String className = p.getClass().getName();
Person p2 = (Person)Class.forName(className).newInstance();
}catch (Exception e){
e.printStackTrace();
}
通过获取一个实例名字的方式,可以创建该实例的一个无参实例,但是这个方法在Java9中被标记被Deprecated,意味着用这个函数是不安全的,随时可能在以后的版本被删除,那么,我们可以用新的版本
try {
Person p = new Person("Jack");
String className = p.getClass().getName();
Constructor<Person> p_con = (Constructor<Person>) Class.forName(className).getConstructor();
Person p2 = p_con.newInstance();
Person p3 = p_con.newInstance("Carl");
} catch (Exception e) {
e.printStackTrace();
}
就是通过Constructor这个类来进行这样的构造.
1.3 通过反射来分析类
对于反射机制,其最重要的内容和功能,就是检查类的结构,通过反射,我们可以了解到一个类的方法域,数据域.
一个实际的例子
public static void main(String[] args) throws Exception{
String className;
if(args.length>0) className = args[0];
//在此处可以通过运行程序时的参数 获取类名
else{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println(">>>");
className = br.readLine();
}
Class classRe = Class.forName(className);
Class superClassRe = classRe.getSuperclass();//是为了获取它的父类以提供继承的情况
String modifiers = Modifier.toString(classRe.getModifiers());
if(modifiers.length()>0) System.out.println(modifiers+" ");
System.out.println("class "+className);
if(superClassRe!=null && superClassRe != Object.class) System.out.println(" extends "+superClassRe.getName());
//获取了它的父类之后,当其父类不为空或者为Object类时,就将他的直接父类打印出来
{
System.out.print("\n{\n");
}
//TODO:打印构造器
System.out.println();
//TODO:打印公有 私有成员
System.out.println();
//TODO:打印数据
System.out.println("}");
}
在此处 我们先将一个类名传入(完整类名 如java.io.BufferedReader),用forName方法返回一个待反射的Class对象,然后获取其父类.Modifier.toString()方法是用来分析一个类,方法或者数据成员的修饰符使用情况,接着将其打印.后面给出了三个TODO,其代码分别在下面给出
打印构造器
public static void printConstructors(Class cl){
Constructor[] constructors = cl.getDeclaredConstructors();
//通过getDeclaredConstructors()方法获取这个类的构造器
for(Constructor c:constructors){
String constructorName = c.getName();
System.out.println(" ");
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length()>0) System.out.println(modifiers+" ");
System.out.println(constructorName+"(");
Class[] paramTypes = c.getParameterTypes();
for(int j = 0 ;j<paramTypes.length;j++){
if(j>0) System.out.println(", ");
System.out.println(paramTypes[j].getName());
}
System.out.println(");");
}
}
打印方法
public static void printMethods(Class cl){
Method[] methods = cl.getMethods();
//通过getDeclaredConstructors()方法获取这个类的构造器
for(Method m:methods){
Class returnType = m.getReturnType();
String methodName = m.getName();
System.out.println(" ");
String modifiers = Modifier.toString(m.getModifiers());
if(modifiers.length()>0) System.out.println(modifiers+" ");
System.out.println(returnType.getName());
System.out.println(methodName+"(");
Class[] paramTypes = m.getParameterTypes();
for(int j = 0 ;j<paramTypes.length;j++){
if(j>0) System.out.println(", ");
System.out.println(paramTypes[j].getName());
}
System.out.println(");");
}
}
打印方法和打印构造器的实现方式非常相似,不同的就是打印方法的函数需要将其返回类型打印出来.
打印类成员
public static void printFields(Class cl){
Field[] fields = cl.getDeclaredFields();
for(Field f:fields){
Class type = f.getType();
String name = f.getName();
System.out.println(" ");
String modifiers = Modifier.toString(f.getModifiers());
if(modifiers.length()>0) System.out.println(modifiers+" ");
System.out.println(type.getName()+" "+name+" ");
}
}
通过这几个方法,你就可以打印出一个类的各种属性了.当然,打印出来的结果或许不那么称心如意,我们将在文章的后面给出一个改进的方法.
1.4 通过反射来分析对象
在上一节中,给出了一个分析类的方法和数据成员的方法,那么,可不可以在运行时通过反射来分析一个对象的各个成员,具体来说,就是通过反射来实现一个对象的getter/setter方法呢?答案当然是可以的.与上一节相似,我们也通过一个例子来入手
在这个例子中,我们通过反射机制来实现一个通用的toString方法,让被传入的成员可以通过这个方法返回一个关于其成员的字符串
public String toString(Object obj) {
if (obj == null) return "";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
//将一个obj对象转成Class对象
if (cl == String.class) return (String) obj;//当其为String类型的时候,直接返回
if (cl.isArray()) {
//对数组的情况要额外处理
StringBuilder r = new StringBuilder(cl.getComponentType() + "[] {");
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r.append(",");
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r.append(val);
//判断是否为基本类型,如果是基本类型,就直接添加,如果不是,就将这个对象
// 通过递归toString方法转成字符串
else r.append(toString(val));
}
return r.toString() + "}";
}
String r = new String(cl.getSimpleName());
do {
r += "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
//通过这个设置可以让程序访问所有的域
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
//在这里我们选择不打印静态域,就是static修饰的成员
if (!r.endsWith("[")) r += ",";
r += f.getName() + " = ";
//获取每个属性的名字
try {
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
//检查这个域的类型是不是基本类型,如果是基本类型的话,就直接添加,这样可以减少
//调用toString方法的次数,提升速度
else r += toString(val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
} while (cl != null);
return r;
}
接下来详细讲一下这个方法.对这个方法传入一个对象(根据Java的自动装箱机制,如果传入的是基本类型,会自动转成他们的包装类).判断这个对象是否已经在visited数组里面,如果已经是被转换过的对象,就直接返回… 然后判断是否数组,将数组里面的对象用{}包裹起来.对于基本的对象,就获取其域名字和值,最后返回字符串.
1.5 通过反射来调用方法
在C/C++中,可以通过函数指针来调用函数,而Java中,则可以通过反射来达到同样的效果(虽然会复杂得多)
首先,我们需要获取一个方法,在Java中,获取方法可以通过Methon类来解决:
在Person类中,我们定义了两个方法
public static void sayHello(){
System.out.println(" Hello");
}
public void saySomething(String message){
System.out.println(name+" "+message);
}
其中sayHello是静态方法,saySomething是非静态的方法
我们先获取Method对象
Method m1 = Person.class.getMethod("sayHello");
Method m2 = Person.class.getMethod("saySomething", String.class);
在Class类中,通过getMethod方法,并指定方法名和参数的类型,可以得到一个方法变量(需要处理特定异常)
m1.invoke(null);
m2.invoke(null,"ABCD");
在这两个语句中,都用到了一个invoke方法,我们先观察一下invoke方法的声明:
Object invoke(Object obj, Object... args)
invoke方法中,第一个参数obj是运行方法时对应的对象,args是Object类数组
比如我们invoke对应的是方法f1,那么我们传入参数,就相当于调用 obj.f1(args[0],args[1]…),如果调用的是静态方法,那么obj可以传入该类的对象,或者null.如果调用的是非静态的方法,那么obj必须传入一个该类的对象,并且如果方法中用到了关于类成员变量,那么就会与传入的类的对象中的成员变量进行对应.
再说说它的返回值,如果调用的是一个void方法,那么就没什么好担心的,如果调用的方法具有返回值,就需要在其前面用强制转型,基本类型需要用其包装类.
调用代码例子如下:
Method m1 = Person.class.getMethod("sayHello");
Method m2 = Person.class.getMethod("saySomething", String.class);
Person p = new Person("Jack");
m1.invoke(null);
m2.invoke(p,",Hi!");
输出结果:
Hello
Jack ,Hi!
2 对反射方法的总结
反射机制使得人们可以通过在运行时查看域和方法, 让人们编写出更具有通用性的程序。
这种功能对于编写系统程序来说极其实用,但是通常不适于编写应用程序。反射是很脆弱的,
即编译器很难帮助人们发现程序中的错误, 因此只有在运行时才发现错误并导致异常。
对于反射来说,我认为在进行一个框架,引擎编写的时候,会大量地用到反射技术,它可以方便地看到一个类中的成员和方法,但是其缺点在于,如果要在应用程序中使用反射,要是在调用时出了差错,在编译期时无法看得到其中的错误,必须在运行时才可以知道是否成功调用,这对于某些应用来说是十分敏感的.
下面给出一些Class类的常用方法
1.名称信息
方法 | 描述 |
---|---|
public String getName() | 获取完整的类名,包括包名 |
public String getSimpleName() | 只获取类名,不包括包名 |
public String getCanonicalName() | 返回更友好的名称(良好地显示数组) |
public String getPackage() | 返回包信息 |
2.字段信息
Class类方法
方法 | 描述 |
---|---|
public Field[] getFields() | 返回所有的public字段,包括父类 |
public Field[] getDeclaredField() | 返回所有的public字段,不包括父类 |
public Field getField(String name) | 返回该类或者父类中指定名称的public字段 |
public Field getDeclaredField() | 返回该类中声明的指定名称的public字段 |
Field类方法
方法 | 描述 |
---|---|
public String getName() | 获取字段的名称 |
public boolean isAccessible() | 是否有该字段的访问权限 |
public void setAccessible(boolean flag) | 设为true表示忽略访问权限控制,可以访问private方法 |
public Object get(Object obj) | 获取指定对象中字段的值 |
public void set(Object obj,Object value) | 将指定对象obj中该字段的值换成value |
3.方法信息
Class类方法
方法 | 描述 |
---|---|
public Method[] getMethods() | 返回所有的public字段,包括父类 |
public Method[] getDeclaredMethod() | 返回所有的public字段,不包括父类 |
public Method getMethod(String name,Class<?>… parameterTypes) | 返回该类或者父类中指定名称的public字段 |
public Method getDeclaredMethod() | 返回该类中声明的指定名称的public字段 |
Method类方法
方法 | 描述 |
---|---|
public String getName() | 获取字段的名称 |
public Object invoke(Object obj, Object… args) | 在上方已经介绍过 |
public void setAccessible(boolean flag) | 设为true表示忽略访问权限控制,可以访问private方法 |
3.完整的反射机制类分析应用代码
/**
* 这个类利用反射机制,通过用户输入的类名,获取这个类的方法与数据并打印出来
*/
public class GetClassName {
public static void main(String[] args) throws Exception{
String className;
if(args.length>0) className = args[0];
//在此处可以通过运行程序时的参数 获取类名
else{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print(">>>");
className = br.readLine();
}
Class classRe = Class.forName(className);
Class superClassRe = classRe.getSuperclass();//是为了获取它的父类以提供继承的情况
Class[] interfaceClassRe = classRe.getInterfaces();
String modifiers = Modifier.toString(classRe.getModifiers());
if(modifiers.length()>0) System.out.print(modifiers+" ");
System.out.print("class "+classRe.getSimpleName());
if(superClassRe!=null && superClassRe != Object.class) System.out.print(" extends "+superClassRe.getSimpleName());
System.out.print(" implements");
for(Class in :interfaceClassRe){
System.out.print(" "+in.getSimpleName());
}
//获取了它的父类之后,当其父类不为空或者为Object类时,就将他的直接父类打印出来
{
System.out.print("\n{\n");
}
//TODO:打印构造器
printConstructors(classRe);
System.out.println();
//TODO:打印公有 私有成员
printFields(classRe);
System.out.println();
//TODO:打印数据
printMethods(classRe);
System.out.println("}");
}
public static void printConstructors(Class cl){
Constructor[] constructors = cl.getDeclaredConstructors();
//通过getDeclaredConstructors()方法获取这个类的构造器
for(Constructor c:constructors){
c.setAccessible(true);
String constructorName = cl.getSimpleName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length()>0) System.out.print(modifiers+" ");
System.out.print(constructorName+"(");
Parameter[] parameters = c.getParameters();
for(int j = 0 ;j<parameters.length;j++){
if(j>0) System.out.print(", ");
System.out.print(parameters[j].getType().getSimpleName()+" "+parameters[j].getName());
}
System.out.println(");");
}
}
public static void printMethods(Class cl){
Method[] methods = cl.getMethods();
//通过getDeclaredConstructors()方法获取这个类的构造器
for(Method m:methods){
m.setAccessible(true);
Class returnType = m.getReturnType();
String methodName = m.getName();
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if(modifiers.length()>0) System.out.print(modifiers+" ");
System.out.print(returnType.getSimpleName()+" ");
System.out.print(methodName+" (");
m.getParameters();
Parameter[] paramTypes = m.getParameters();
for(int j = 0 ;j<paramTypes.length;j++){
if(j>0) System.out.print(", ");
System.out.print(paramTypes[j].getType().getSimpleName()+" "+paramTypes[j].getName());
}
System.out.println(");");
}
}
public static void printFields(Class cl){
Field[] fields = cl.getDeclaredFields();
for(Field f:fields){
f.setAccessible(true);
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if(modifiers.length()>0) System.out.print(modifiers+" ");
System.out.print(type.getSimpleName()+" "+name+" ");
System.out.println(";");
}
}
}