JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性,方法,泛型类型;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
以上两句话是借用百度百科的描述,java反射机制使得我们可以在运行的时候获取这个类的几乎所有的信息,及调用方法,但不能改变类的结构,所以,java反射机制使得java有点像Ruby,Python这种动态语言,但不是动态语言。
在java中,一个类要想使用必须经过类的加载,连接和初始化这3个操作,正常情况下这个3个步骤都有jvm来完成。
类的加载:类的加载是指定将类的class文件读入内存,并为之生成一个java.lang.Class对象,程序使用的每个类都会在内存中为之生成一个java.lang.Class对象。
other ClassLoader:其他类加载器,如URLClassLoader
除了系统向我们提供的类加载器外,我们还可以自定义我们自己的类加载器,自定义类加载器用的不多
类的连接:当类的加载完成后,系统为之在内存中生成了Class对象,我们知道内存中保存的都是二进制数据,所有类的连接时将类的二进制数据放到jre(java runtime environment )中,这个过程分为如下三个阶段:
类的初始化:这个阶段,jvm负责对类进行初始化,对Field进行初始化赋值,对Field初始化有如下两种方式:
在静态代码块(static{})中对Field初始化赋值
ps:动态代码块({})中的代码块在这个阶段不会本执行,它是在生成类的实例的时候执行,相当于构造方法中的语句,用的不多
用图画出类初始化过程如下:
这里对上面的这个图做个简要说明,当要初始化的类有父类时,先初始化其直接父类,初始化其直接父类的时候又会判断其直接父类是否有父类,然后依次递归,直到要初始化的类的所有的父类都被初始化后在初始化最初要初始化的类
ps:有初始化语句指的的成员变量直接赋值,及静态代码块等等,当创建某个类的实例,调用了某个类的静态方法,访问某个类或接口的Field或者为之赋值,该类的子类被初始化等等都会导致类初始化
前面说了类的加载器有很多,系统为我们提供了四种常用的类加载器,下面我们看看这四种常用的类加载器各自的分工及如何协调工作的
运行如下代码:
/** * 根类加载器bootstrapClassLoader,不是ClassLoader的子类,是由JVM自身实现的 * @author lt * */ public class BootstrapTest { public static void main(String[] args) { // 获取根类加载器所加载的所有url数组 URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs(); //输出根类加载器加载的全部的url for(int i = 0;i<urLs.length;i++){ System.out.println(urLs[i].toExternalForm()); } } }
控制台输出:
file:/D:/Program%20Files/Java/jre7/lib/resources.jar file:/D:/Program%20Files/Java/jre7/lib/rt.jar file:/D:/Program%20Files/Java/jre7/lib/sunrsasign.jar file:/D:/Program%20Files/Java/jre7/lib/jsse.jar file:/D:/Program%20Files/Java/jre7/lib/jce.jar file:/D:/Program%20Files/Java/jre7/lib/charsets.jar file:/D:/Program%20Files/Java/jre7/lib/jfr.jar file:/D:/Program%20Files/Java/jre7/classes
我们可以看到根类加载器所要加载的所有url
Extension ClassLoader(扩展类加载器):
负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext和有java.ext.dirs系统属性指定的目录)中jar包的类
Application ClassLoader:
系统类加载器,负责加载程序员自己写的类
其他类加载器:
加载自己写的类,如:URLClassLoader既可以加载本地的类也可以加载远程的类
类加载器之间的协调工作:
说明:
自定义类加载器:
自定义类加载器非常简单,前面说了java类加载器中除了根类加载器不是ClassLoader外,其他类加载器都是ClassLoader的子类,ClassLoader中有大量的protected的方法,毫无疑问,我们可以通过继承ClassLoader来扩展类加载器,从而实现我们自己的类加载器。
通常我们如果重写loadClass方法时使自定义ClassLoader变得困难,我们还得自己实现该类的父类委托,缓冲机制两种策略,而loadClass的执行过程如下:
findLoader(String)
来检查该类是否已经加载,如果加载了则直接返回该类的Class对象,否则返回Null loadClass()
方法,如果父类加载器为Null,则使用根类加载器 我们可以看到前面两步是实现父类的一些逻辑,其实这里两步实现了父类委托和缓冲机制策略,而我们只需要重新 findClass(String)
方法来实现我们自己的逻辑即可,这样使自定义类加载器就简单多了
前面说了类的加载器,下面开始真正学习java反射机制,java反射机制可以时我们在运行时刻获取类的信息,如:类的成员变量类型,值,方法的信息及调用方法,获取泛型类型,获取注解等等。
java反射的相关api都在 java.lang.reflect
包下:学习api最好的方法还是看官网文档,因为官方文档最权威。下面我们通过一个测试类来学习反射的最常用的知识。
// 通过Class的静态方法forName加载类,该方法会初始化类 Class clazz = Class.forName("Common"); // 通过反射生成该类的实例,调用public的无参构造方法,反射生成类的实例,该类必须得有一个public的无参方法 Object newInstance = clazz.newInstance();
public static void testConstructor(Class clazz){ System.out.println("-----------构造方法测试-----------"); // 获取所以的构造方法 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for(int i=0;i<declaredConstructors.length;i++){ int index = i+1; // 因为i从0开始 String str = "第"+index+"个构造方法权限修饰符为"; if(declaredConstructors[i].getModifiers() == 1){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="public"; } if(declaredConstructors[i].getModifiers() == 2){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="private"; } if(declaredConstructors[i].getModifiers() == 4){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="protected"; } str+="名称为:"+declaredConstructors[i].getName(); System.out.println(str); } }
获取类方法的相关信息 ```java /**
@param clazz */ public static void testCommonMethodInfo(Class clazz){ System.out.println("-----------普通方法测试-----------"); Method[] methods = clazz.getMethods(); System.out.println(methods.length); for(int i=0;i<methods.length;i++){
int dexI = i+1; String str = "第"+dexI+"个方法形式为:"; // 获取方法权限修饰符 int modifiers = methods[i].getModifiers(); if(modifiers == 1){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="public"; } if(modifiers == 2){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="private"; } if(modifiers == 4){ // 这里的常量值可以查看api文档,博客下面也会帖子各种修饰符对应的数字 str+="protected"; } String returnType = methods[i].getReturnType().getSimpleName(); // 获取方法返回类型 str+=" "+returnType+" "; String name = methods[i].getName(); // 获取方法名称 str+= name; str+="("; // 获取方法参数的类型 Class[] parameterTypes = methods[i].getParameterTypes(); for(int j=0;i<parameterTypes.length;i++){ str+=parameterTypes[j]+" "; } str+=")"; System.out.println(str);
} }
- 调用类的方法 ```java /** * 通过反射调用方法 * @param clazz * @throws SecurityException * @throws NoSuchMethodException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws IllegalAccessException */ public static void testMethodInvoke(Class clazz,Object newInstance) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ System.out.println("-----------测试方法的调用-----------"); /** * 由于我们知道类中有这种类型的方法,所以我们直接指定要获取的方法类型 * 如果不知道我们要调用的 方法的类型,我们可以获取方法的参数类型等等所有信息 * 然后匹配到我们需要调用的方法,这里做个说明,我们要调用的方法名是知道的 */ Method sum = clazz.getMethod("sum",int.class,int.class); System.out.println(sum); sum.invoke(newInstance, 1,1); Method setS = clazz.getMethod("setS",String.class); System.out.println(setS); setS.invoke(newInstance, "赋值啦"); }
/** * 测试类的Field * @param clazz * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void testField(Class clazz,Object obj) throws IllegalArgumentException{ System.out.println("-----------测试类的Field-----------"); // 获取所以的Field,包括private修饰的,但不能直接获取和直接改变private修饰的值 Field[] fields = clazz.getDeclaredFields(); for(int i=0;i<fields.length;i++){ int dexI = i+1; System.out.print("第"+dexI+"个name:"+fields[i].getName()+" "); Class type = fields[i].getType(); String typeStr = type.getName(); try{ if(typeStr.equals("int")){ System.out.println("value:"+fields[i].getInt(obj)); }else if(typeStr.equals("java.lang.String")){ // 字符串形式通过get(Object obj)方法取得,如果该Field的权限为private,则 获取值的时候会报java.lang.IllegalAccessException System.out.println("value:"+fields[i].get(obj)); } }catch(IllegalAccessException ex){ System.out.println("不能获取private修饰的属性值"); } } }
/** * 测试注解(类上的注解),属性方法上的注解分别通过 * Field对象和Method对象的getAnnotations()方法可以得到 * 和这里是一样的 * @param clazz */ public static void testAnnotation(Class clazz){ System.out.println("-----------测试注解-----------"); Annotation[] annotations = clazz.getAnnotations(); for(int i=0;i<annotations.length;i++){ System.out.println(annotations[i]); } }
/** * 泛型测试 * @throws NoSuchFieldException */ private static void genericTest() throws NoSuchFieldException { System.out.println("-----------获泛型测试-----------"); Class<ReflectTest> clazz = ReflectTest.class; Field f = clazz.getDeclaredField("map"); // 直接使用getType()只对普通类型有效,并不能得到有泛型的类型 Class<?> type = f.getType(); // 下面的代码可以看到只输出了java.util.Map System.out.println(" map 的类型为:"+type); // 获取Field实例f的泛型类型 Type genericType = f.getGenericType(); // 如果genericType是ParameterizedType对象 if(genericType instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) genericType; // 获取原始类型 Type rawType = parameterizedType.getRawType(); System.out.println("原始类型是:"+rawType); // 取得泛型类型的泛型参数 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for(int i=0;i<actualTypeArguments.length;i++){ System.out.println("第"+i+"个泛型类型为:"+actualTypeArguments[i]); } }else{ System.out.println("泛型类型获取出错"); } }
/** * 获取泛型T的实际类型 */ public static void getTType(){ System.out.println("-----------获取泛型T的实际类型-----------"); Dog dog = new DogImpl(); // 注意,下面这种写法不能获取到T的实际类型,因为范式要在编译的时候就指定类型,在运行时候指定类型是获取不到真实类型的 // Dog<Cat> dog = new Dog<Cat>(); }
修饰符常量对应的值:
由于注释说了很清楚了,所以这里就不过多介绍了,以上这些方法都是参考了api提供的一些方法自己写的一些测试,其实都只是一个简单方法的使用,但这些方法是反射中最基本最常用的方法,api方法很多,所以我们学习api的时候最好时刻查询文档,查看文档是个好习惯。
总结,java反射机制是java的一个非常重要的一个知识点,其实向spring,struts等等一些知名的框架没有一个没有使用反射,所以学好java反射是提升我们技术必不可少的,我们应该掌握java反射机制。这篇文章与其说是java反射机制的讲解,其实是我自己学习java反射的学习总结,因为学习java也好久了,回顾总结一下所写的知识点,所以文章内容有说错的部分,欢迎指出。