火跳动着绝望/谁在低声吟唱/说遗忘者的哀伤 /用战斗证明希望
说起 Java 中的反射机制,初学者或者 Java 基础不扎实的人或许会有点懵,甚至唯恐避之不及。事实上,Java 的反射机制很重要,许多开发框架都是基于反射来实现对目标对象的操作,反射配合注解,用好用活,能大大提升代码的效率和性能,并且,如今 Android 中一些高效的注解库也与此反射机制有着极大的关联,这期就来探究并总结下。
简介反射 说及反射,先说说内省。
内省
指计算机程序在运行时 (Run time) 检查对象 (Object) 类型的一种能力,也称为运行时类型检查。
反射
指计算机程序在运行时 (Run time) 可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,程序能够”观察“并且修改自己的行为。
一些编程语言比如 Java 具有反射特性,而 C++ 不具有反射特性只具有内省特性。
以上来源于维基百科。
就 Java 中的反射,其可以让我们在运行时获取类的方法、属性、父类和接口等 Class 内部的信息。同时,其还可以让我们在运行期实例化对象,调用方法,如通过调用 get/set 方法获取变量的值,甚至属性或方法是私有的也可以。其中,“看透 Class” 的能力即为内省了。反射多用于以下场景:
场景一:也是使用比较多的场景,要使用的类在运行时才会确定,这时不能在编译期使用,只能通过反射的形式来使用在运行时才存在的类(符合某种特定规范的类,如 JDBC 即 Java 数据库连接)。
场景二:比较常见的场景,编译时对类的内部信息不知道,必须到运行时才能获得类的具体信息(如 ORM 即对象关系映射框架,在运行时才能获取类中的各个属性,再通过反射的形式获取其属性名和值,存入数据库)。
关于 Class 上文所指的 Class,结合示意图如下所示:
当一个 Java 项目被编写完,所有的 Java 文件会被编译成一个 .class 文件,这些 Class 对象承载了这个类型的父类、接口、构造函数、方法和属性等原始信息,随后,这些 .class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载后,Java 虚拟机就会在内存中自动产生一个 Class 对象。事实上,通过 new 的形式创建对象就是通过这些 Class 来创建,只是过程对外界不透明。
应用实例 先给出以下代码:
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Person { String mName; public Person (String name ) { this .mName = name; } private void sayHello (String friendName ) { System.out .println(mName + " say hello to " + friendName); } protected void showMyName ( ) { System.out .println("My name is " + mName); } public void breath ( ) { System.out .println("take breath" ); } }
Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Student extends Person implements Examination { int mGrade; public Student (String name) { super (name); } public Student (int grade, String name) { super (name); this .mGrade = grade; } private void study (String course) { System.out.println(mName + " study " + course); } public void takeAnExamination () { System.out.println("takeAnExamination" ); } public String toString () { return "Student: " + mName; } }
Breath.java
1 2 3 4 public interface Breath { public void breath ( ) ; }
Examination.java
1 2 3 4 public interface Examination { public void takeAnExamination ( ) ; }
反射 Class 以及构造对象
I. 获取 Class 对象
想检查一个类的信息之前,首先需要获取类的 Class 对象。Java 中,所有类型,即使是数组,都有与之关联的 Class 类的对象。若在编译期知道一个类的名字,则可以使用如下方法获取一个类的 Class 对象:
1 Class<?> myObjectClass = MyObject.class;
若得到了某个对象,但是想获取该对象的 Class 对象,示例如:
1 2 Student me = new Student("Iamasoldier6" ); Class<?> clazz = me.getClass();
若在编译期获取不到目标类型,但是知其完整类路径,则可以通过如下的形式来获取 Class 对象:
1 Class<?> myObjectClass = Class.forName("com.iamasoldier6.Student" );
以上在使用 Class.forName() 方法时,必须提供一个类的全名,包括类所在包的名字,如 Student 类位于 com.iamasoldier6 包,则其完整类路径为 com.iamasoldier6.Student。另外,调用 Class.forName() 方法时,未在编译路径 (classpath)下找到对应的类,则会抛出 ClassNotFoundException。
接口说明
1 2 3 4 5 6 public static Class<?> forName(String className); public static Class<?> forName(String className, boolean shouldInitialize, ClassLoader classLoader);
II. 通过 Class 对象构造目标类型的对象
拿到 Class 对象后,通过 Class 对象构造出该类型的对象,然后通过该对象实现其功能。在 Java 中,要构造对象,须通过该类的构造函数,反射也如此。但是也有区别,通过反射构造对象,首先要获取类的 Constructor (构造器)对象,再通过 Constructor 创建目标类的对象,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private static void classForName() { try { Class <?> clazz = Class .forName("com.iamasoldier6.Student" ); Constructor<?> constructor = clazz.getConstructor(String.class ); Object obj = constructor.newInstance("Iamasoldier6" ); System.out.println ("obj: " + obj.toString()); } catch (Exception e) { e.printStackTrace(); } }
以上方式使得我们可以在运行时通过完整的类名来构建对象。
接口说明
1 2 3 4 5 6 public Constructor<T> getConstructor (Class...<?> parameterTypes) ; public Constructor[]<?> getConstructors();
当通过反射获取到 Constructor、Method、Field 后,在反射调用前将该对象的 accessible 标志设置为 true,以此来提升反射速度:值为 true,则指示反射的对象在使用时取消 Java 语言的访问检查;值为 false,则指示反射的对象在使用时实施 Java 语言的访问检查。示例如下:
1 2 3 4 5 6 Constructor<?> constructor = clazz.getConstructor(String.class); constructor.setAccessible(true ); Method studyMethod = Student.class.getMethod("study" , String.class);
反射获取类中的方法
I. 获取当前类中定义的方法
通过 Class 中的 getDeclaredMethods() 方法获取当前类中定义的所有方法,即获取到当前类中的 public、default、protected、private 的所有方法。其中,getDeclaredMethod(String name, Class…<?> parameterTypes) 是获取某个指定的方法。代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static void showDeclaredMethods() { Student student = new Student ("Iamasoldier6" ); Method [] methods = student.getClass().getDeclaredMethods(); for (Method method : methods) { System .out .println("Declared method name: " + method .getName()); } try { Method studyMethod = student.getClass().getDeclaredMethod("study" , String .class); // 获取方法的参数类型列表 Class <?>[] paramClasses = studyMethod.getParameterTypes(); for (Class <?> class1 : paramClasses) { System .out .println("Study 方法的参数类型:" + class1.getName()); } // 是否是 private 方法 System .out .println(studyMethod.getName() + " is private " + Modifier .isPrivate(studyMethod.getModifiers())); studyMethod.invoke(student, "java" ); } catch (Exception e) { e.printStackTrace(); } }
II. 获取当前类以及父类中定义的公有方法
通过 Class 中的 getMethods() 方法获取当前类以及父类中定义的公有方法。其中,getMethod() 是获取某个指定的方法。代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private static void showMethods() { Student student = new Student ("Iamasoldier6" ); Method [] methods = student.getClass().getMethods(); for (Method method : methods) { System .out .println("Method name: " + method .getName()); } try { // 通过 getMethod() 只能获取公有方法,若获取私有方法则抛出异常,比如这里会抛异常 Method studyMethod = student.getClass().getMethod("study" , String .class); // 是否是 private System .out .println(studyMethod.getName() + " is private " + Modifier .isPrivate(studyMethod.getModifiers())); studyMethod.invoke(student, "java" ); } catch (Exception e) { e.printStackTrace(); } }
接口说明
1 2 3 4 5 6 7 8 9 10 11 public Method getDeclaredMethod (String name, Class...<?> parameterTypes) ; public Method[] getDeclaredMethods();public Method getMethod (String name, Class...<?> parameterTypes) ;public Method[] getMethods();
getDeclaredMethod() 和 getDeclaredMethods() 包含 private、protected、default、public 的方法,通过这两个方法获取到的只是在自身中定义的方法,从父类中继承的方法无法获取。
getMethod() 和 getMethods() 只包含 public 方法,父类中的公有方法可以获取。
反射获取类中的属性
获取属性与上文中的获取方法是很相似的,这里,getField() 类比之前的 getMethod(),getDeclaredField() 类比之前的 getDeclaredMethod()。
I. 获取当前类中定义的属性
通过 Class 中的 getDeclaredFields() 方法获取当前类中定义的所有属性,会获取到当前类中 public、default、protected、private 的所有属性,getDeclaredField() 方法则是获取某个指定的属性。代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static void showDeclaredFields ( ) { Student student = new Student("Iamasoldier6" ); Field[] publicFields = student.getClass().getDeclaredFields(); for (Field field : publicFields) { System.out .println("Declared field name: " + field.getName()); } try { Field gradeField = student.getClass().getDeclaredField("mGrade" ); System.out .println("My grade is: " + gradeField.getInt(student)); gradeField.set (student, 10 ); System.out .println("My grade is: " + gradeField.getInt(student)); } catch (Exception e) { e.printStackTrace(); } }
II. 获取当前类以及父类中定义的公有属性
通过 Class 中的 getFields() 方法获取当前类以及父类中所有的公有属性。其中,getField() 是获取某个指定的属性。代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static void showFields ( ) { Student student = new Student("Iamasoldier6" ); Field[] publicFields = student.getClass().getFields(); for (Field field : publicFields) { System.out .println("Field name: " + field.getName()); } try { Field ageField = student.getClass().getField("mAge" ); System.out .println("Age is: " + ageField.getInt(student)); } catch (Exception e) { e.printStackTrace(); } }
接口说明
1 2 3 4 5 6 7 8 9 10 11 public Method getDeclaredField (String name) ; public Method][] getDeclaredFields();public Method getField (String name) ;public Method[] getFields();
getDeclaredField() 和 getDeclaredFields() 包含 private、protected、default 和 public 的属性,并且,通过这两个函数获取到的只是在自身中定义的属性,从父类中继承的属性无法获取到。
getField() 和 getFields() 只包含 public 属性,父类中的公有属性可以获取到。
反射获取父类与接口
I. 获取父类
获取 Class 对象的父类,代码示例如下:
1 2 3 4 5 6 7 8 9 10 private static void showSuperClass ( ) { Student student = new Student("Iamasoldier6" ); Class<?> superClass = student.getClass().getSuperClass(); while (superClass != null ) { System.out .println("Student's super class is: " + superClass.getName()); superClass = superClass.getSuperClass(); } }
II. 获取接口
获取 Class 对象中实现的接口,代码示例如下:
1 2 3 4 5 6 7 8 private static void showInterfaces() { Student student = new Student("Iamasoldier6" ); Class <?>[] interfaces = student.getClass().getInterfaces(); for (Class <?> class1 : interfaces) { System.out.println ("Student's interface is: " + class1.getName()); } }
反射获取注解信息
框架开发中,注解加反射的组合使用最为常见,定义注解时会通过 @Target 指定该注解能够作用的类型,示例如下:
1 2 3 4 5 6 7 8 @Target ({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE }) @Retention (RetentionPolicy.RUNTIME)static @interface Test { }
以上注解的 @Target 表示该注解只能用在函数上。
事实上,通过反射能获取一个 Class 对象,以获取类型、属性和方法等相关的对象,通过这些对象的 getAnnotation 接口获取到对应的注解信息。
首先,在目标对象上添加注解,代码如下:
1 2 3 4 5 6 7 8 9 @Test (tag = "Student class Test Annotation" )public class Student extends Person implements Examination { @Test (tag = "mGrade Test Annotation" ) int mGrade; }
再通过相关的注解方法得到注解信息,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private static void getAnnotationInfos() { Student student = new Student("Iamasoldier6" ); Test classTest = student.getClass().getAnnotation(Test.class ); System.out .println("Class annotation tag = " + classTest.tag()); Field field = null ; try { field = student.getClass().getDeclaredField("mGrade" ); Test testAnnotation = field.getAnnotation(Test.class ); System.out .println("Test 注解 tag: " + testAnnotation.tag()); } catch (Exception e) { e.printStackTrace(); } }
输出结果:
1 2 Class annotation tag = Student class Test AnnotationTest 注解 tag: mGrade Test Annotation
接口说明
1 2 3 4 public <A extends Annotation> A getAnnotation (Class<A> annotationClass) ;public Annotation[] getAnnotations();
至此,关于 Java 中的反射机制探究并总结完毕。
本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。
您也可以关注我个人的微信公众号 :码农六哥 ,第一时间获得博客的更新通知,或后台留言与我交流 。
参考文献 1.http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html
2.http://t.cn/Rf1q5Ej