好吧,我知道这听起来还是很模糊,让我们一步一步来:
Class a
我们称a属于“类”类型(Class类型) 所以我们可以其实可以将java中的对象分为两种: 1. 实例对象 2. Class对象 所以我们今天要讲的第一个内容是: 有别于平时使用的实例对象的——Class对象
public class MyClass { }
那么取得该类对应Class对象的方法有三种: 一. 通过“类名.class”的方式取得
Class classInstance= MyClass.class;
二. 通过类创建的实例对象的getClass方法取得
MyClass myClass = new MyClass(); Class classInstance = myClass.getClass();
三.通过Class类的静态方法forName方法取得(参数是带包名的完整的类名)
Class classInstance = Class.forName("mypackage.MyClass");
【注意】 1.运行forName时候可能会因为找不到包名而抛出已检查异常ClassNotFoundException,所以我们需要将其包裹在try-catch语句中:
try { Class classInstance = Class.forName("mypackage.MyClass"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }
2.上面三种方法取得的对象都是相同的,所以效果上等价
我们可以通过一系列的方法,从一个类的Class对象中取得对应的Method对象,Field对象和Constructor对象 假设c是一个类的Class对象: 通过 c.getDeclaredMethods()可取得这个类中所有声明方法对应的Method对象组成的数组 通过 c.getDeclaredFields()可取得这个类中所有声明的成员变量对应的Field对象组成的数组 通过 c.getConstructors(); 可取得这个类中所有构造函数所对应的Constructor对象所组成的数组 在下面的示例中,我们将遍历某一个类中方法,成员变量和构造器的名称: MyClass.java:
public class MyClass { private int value; //成员变量 public MyClass (int value) { this.value = value; } //构造函数 public int getValue() { return value; } //方法1 public void setValue(int value) { this.value = value; } //方法2 }
Test.java:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printClassMessage (Object obj) { Class c = obj.getClass(); // 获取obj所属类的Class对象
Method [] methods = c.getDeclaredMethods(); // 获取方法对象列表
System.out.println("遍历MyClass类里的所有方法的名称:"); for(int i =0; i<methods.length; i++) { System.out.println(methods[i].getName()); }
Field [] fields = c.getDeclaredFields(); // 获取成员变量对象列表 System.out.println("遍历MyClass类里的所有成员变量的名称:"); for(int i =0; i<fields.length; i++) { System.out.println(fields[i].getName()); } Constructor [] constructors = c.getConstructors(); // 获取构造函数对象列表 System.out.println("遍历MyClass类里的所有构造函数的名称:"); for(int i =0; i<constructors.length; i++) { System.out.println(constructors[i].getName()); } }
public static void main(String [] args) { MyClass myClass = new MyClass(1); // 创建一个MyClass对象 printClassMessage(myClass); // 打印这个对象所属类的相关信息 } }
运行结果:
遍历MyClass类里的所有方法的名称:
getValue
setValue
遍历MyClass类里的所有成员变量的名称:
value
遍历MyClass类里的所有构造函数的名称:
mypackage.MyClass
上面的例子仅仅是作为一个展示,Method/Field/Constructor对象的API当然不仅限于getName这样获取名称的简单操作,所以接下来我将分别介绍更具体的反射API
getValue
setValue
让我们看看通过getMethods打印又会取得什么结果: Test.java:
import java.lang.reflect.Method; public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); Method [] methods = c.getMethods(); for (Method method : methods) { System.out.println(method.getName()); } }
public static void main(String [] args) { MyClass myClass = new MyClass(1); printMethodsMessage(myClass); } }
运行结果:
getValue
setValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
让我们思考一下: 一个方法有哪些“信息”值得(或需要)我们去分析呢? 主要是两部分: 1. 返回值 2. 方法参数 你可能猜的出来, Method对象已经提供了一套API去获取这些信息了,让我们来看看:
import java.lang.reflect.Method; public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所属类对应的Class对象 Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组 for (Method method : methods) { // 遍历Method对象 String name = method.getName(); // 取得方法名 Class returnClass = method.getReturnType(); // 获取方法返回值对应的Class对象 String returnName = returnClass.getName(); //获取返回值所属类的类名——也即返回值类型 System.out.println(name + "方法的返回值类型是" + returnName); } } public static void main(String [] args) { MyClass myClass = new MyClass(1); printMethodsMessage(myClass); }
}
运行结果:
getValue方法的返回值类型是int
setValue方法的返回值类型是void
通过method.getReturnType(),我们取得了该方法的返回值对应的Class对象(哈哈,绕了一圈后还是回到Class对象上了) 然后通过Class对象调用getName方法就取得了返回值所属的类的名称,也即返回值类型
public class MyClass { public void method1 (int a, long b) { }; public void method2 (float a, double b) { }; public void method3 (String str) { }; }
Test.java:
public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所属类对应的Class对象 Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组 for (Method method : methods) { // 遍历Method对象 String methodName = method.getName(); // 取得方法名 String paramsStr = ""; // 用于存放某个方法参数类型列表的字符串 Class [] paramsClasses = method.getParameterTypes(); for (Class pc: paramsClasses) { String paramStr = pc.getName(); // 获取当前参数类型 paramsStr+=paramStr + " "; } System.out.println(methodName+ "方法的所有参数的类型列表:" + paramsStr); } }
public static void main(String [] args) { MyClass myClass = new MyClass(); printMethodsMessage(myClass); } }
运行结果:
method2方法的参数类型列表:float double method1方法的参数类型列表:int long method3方法的参数类型列表:java.lang.String
public class MyClass { private int number = 123; private String name ="彭湖湾"; }
Test.java:
import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printFieldsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所属类对应的Class对象 try { Field field = c.getDeclaredField("name"); // 取得名称为name的field对象 field.setAccessible(true); // 这一步很重要!!!设置为true才能访问私有成员变量name的值! String nameValue = (String) field.get(obj); // 获取obj中name成员变量的值 System.out.println("MyClass类中name成员变量的值为:" + nameValue); // 输出 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
public static void main(String [] args) { MyClass myClass = new MyClass(); printFieldsMessage(myClass); }
}
运行结果:
MyClass类中name成员变量的值为:彭湖湾
Field field = class1.getDeclaredField(number"); System.out.print(field.getType().getName());
运行结果:
int
【注意】:因为java权限的原因,直接读取私有成员变量的值是非法的(加了field.setAccessible(true)后就可以了),但仍可以直接读取私有成员变量的类型
public class MyClass { public MyClass(int a, String str){} }
Test.java:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printContructorsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所属类对应的Class对象 Constructor [] constructors = c.getDeclaredConstructors(); for (Constructor constructor : constructors) { Class [] paramsClasses = constructor.getParameterTypes(); String paramsStr = ""; for (Class pc : paramsClasses) { String paramStr = pc.getName(); paramsStr+=paramStr + " "; } System.out.println("构造函数的所有参数的类型列表:" + paramsStr); } } public static void main(String [] args) { MyClass myClass = new MyClass(1, "彭湖湾"); printContructorsMessage(myClass); }
}
运行结果:
构造函数的所有参数的类型列表:int java.lang.String
保存后,在编译阶段就能够发现:“诶? 好像没有这个类哦!” 报的错误是:
NotExistClass cannot be resolved to a type
但是如果我们用Class.forName("XXX")动态加载一个类呢? 我们发现,保存后,在编译阶段已经不能发现这个错误了,对应的是要捕捉可能发生的异常
用该动态加载的类创建实例对象 Class对象有一个newInstance方法,我们可以用它来创建实例对象
Class classInstance = Class.forName("mypackage.MyClass"); MyClass myClass = (MyClass) classInstance.newInstance();
不过要注意的是,因为newInstance返回的是一个Object,所以要做强制类型转换,将其变成MyClass类型 捕捉可能产生的异常后:
public class Test { public static void main(String [] args) { try { Class classInstance = Class.forName("mypackage.MyClass"); MyClass myClass = (MyClass) classInstance.newInstance(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
全文的总结 如果您已经看到这里了,那么请允许我在用多那么一点罗嗦的文字做个总结: 总结 1.反射为我们提供了全面的分析类信息的能力,例如类的方法,成员变量和构造器等的相关信息,反射能够让我们很方便的获取这些信息, 而实现这个获取过程的关键是取得类的Class对象,然后根据Class对象取得相应的Method对象,Field对象和Constructor对象,再分别根据各自的API取得信息。 2.反射还为我们提供动态加载类的能力 一些细节 1. API中getDeclaredXXX和getXXX的区别在于前者只获取本类声明的XXX(如成员变量或方法),而不获取超类中继承的XXX, 后者相反 2. API中, getXXXs(注意后面的s)返回的是一个数组, 而对应的 getXXX("键")按键获取一个值(这个时候因为可能报已检查异常所以要用try-catch语句包裹) 3. 私有成员变量是不能直接获取到值的!因为java本身的保护机制,允许你取得私有成员变量的类型,但是不允许直接获取值,所以要对对应的field对象调用field.setAccessible(true) 放开权限 最后 反射的API其实还有一堆...但是抱歉的是没办法一一给大家介绍,只能当作个“引子”给大家尝个鲜,另外,本人java小白,有诸多不当之处,还望大家指出,谢谢大家。 【完】 参考资料: 《java核心技术 卷1》—— Cay S. Horstmann, Gary Cornell