反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
总而言之,通过反射,我们可以在 运行时获得程序或程序集中每一个类型的成员和成员的信息
。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性
,这样的对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
运行时 运行时 运行时 运行时
当我们的程序在运行时,需要动态的加载一些类,这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。
例如我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.mySqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName("com.java.dbtest.mySqlConnection");通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!
很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE时,我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架
。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
上面我们提到了反射可以用于判断任意对象所属的类,获得Class对象,构造任意一个对象以及调用一个对象。
其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础,它包含的方法我们在第一部分已经进行了基本的阐述。应用反射时我们最关心的一般是一个类的构造器、属性和方法,下面我们主要介绍Class类中针对这三个元素的方法:
public static Class<?> forName(String className) 比如在 JDBC 开发中常用此方法加载数据库驱动: Class.forName(driver); 复制代码
Class<?> klass = int.class; Class<?> classInt = Integer.TYPE; 复制代码
StringBuilder str = new StringBuilder("123"); Class<?> klass = str.getClass(); 复制代码
一般地,我们用 instanceof
关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class
对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj); 复制代码
Class<?> c = String.class; Object str = c.newInstance(); 复制代码
//获取String所对应的Class对象 Class<?> c = String.class; //获取String类带一个String参数的构造器 Constructor constructor = c.getConstructor(String.class); //根据构造器创建实例 Object obj = constructor.newInstance("23333"); System.out.println(obj); 复制代码
返回某个类的所有 public
方法, 包括自己声明和从父类继承的
。
获取所有本类自己的方法,不问访问权限,不包括从父类继承的方法
方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
/** * 访问对象的私有方法 * 为简洁代码,在方法上抛出总的异常,实际开发别这样 */ private static void getPrivateMethod() throws Exception{ //1. 获取 Class 类实例 TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); //2. 获取私有方法 //第一个参数为要获取的私有方法的名称 //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null //方法参数也可这么写 :new Class[]{String.class , int.class} Method privateMethod = mClass.getDeclaredMethod("privateMethod", String.class, int.class); //3. 开始操作方法 if (privateMethod != null) { //获取私有方法的访问权 //只是获取访问权,并不是修改实际权限 privateMethod.setAccessible(true); //使用 invoke 反射调用私有方法 //privateMethod 是获取到的私有方法 //testClass 要操作的对象 //后面两个参数传实参 privateMethod.invoke(testClass, "Java Reflect ", 666); } } 复制代码
获得类的所有公共构造函数
获得使用特殊的参数类型的公共构造函数,
获得类的所有构造函数
获得使用特定参数类型的构造函数
获得类的所有公共字段
获得命名的公共字段
获得类声明的所有字段
获得类声明的命名的字段
** * 修改对象私有变量的值 * 为简洁代码,在方法上抛出总的异常 */ private static void modifyPrivateFiled() throws Exception { //1. 获取 Class 类实例 TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); //2. 获取私有变量 Field privateField = mClass.getDeclaredField("MSG"); //3. 操作私有变量 if (privateField != null) { //获取私有变量的访问权 privateField.setAccessible(true); //修改私有变量,并输出以测试 System.out.println("Before Modify:MSG = " + testClass.getMsg()); //调用 set(object , value) 修改变量的值 //privateField 是获取到的私有变量 //testClass 要操作的对象 //"Modified" 为要修改成的值 privateField.set(testClass, "Modified"); System.out.println("After Modify:MSG = " + testClass.getMsg()); } } 复制代码
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 复制代码
下面是一个实例
public class test1 { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class<?> klass = methodClass.class; //创建methodClass的实例 Object obj = klass.newInstance(); //获取methodClass类的add方法 Method method = klass.getMethod("add",int.class,int.class); //调用method对应的方法 => add(1,4) Object result = method.invoke(obj,1,4); System.out.println(result); } } class methodClass { public final int fuck = 3; public int add(int a,int b) { return a+b; } public int sub(int a,int b) { return a+b; } } 复制代码
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子:
public static void testArray() throws ClassNotFoundException { Class<?> cls = Class.forName("java.lang.String"); Object array = Array.newInstance(cls,25); //往数组里添加内容 Array.set(array,0,"hello"); Array.set(array,1,"Java"); Array.set(array,2,"fuck"); Array.set(array,3,"Scala"); Array.set(array,4,"Clojure"); //获取某一项的内容 System.out.println(Array.get(array,3)); } 复制代码
其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException { return newArray(componentType, length); } 复制代码
而 newArray 方法是一个 native 方法
private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException; 复制代码