JAVA 反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意方法和属性; 这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制.
一: Class 类
在面向对象的世界里, 万物皆对象. 类也是对象, 类是 java.lang.Class 类的实例对象.
Class 类的实例表示正在运行的 Java 应用程序中的类和接口. 枚举是一种类, 注释是一种接口. 每个数组属于被映射为 Class 对象的一个类, 所有具有相同元素类型和维数的数组都共享该 Class 对象.
基本的 Java 类型 (boolean , byte , char , short , int , long , float 和 double) 和关键字 void 也表示为 Class 对象.
Class 没有公共构造方法. Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的.
上面来自于 JDK 的罗里吧嗦, 下面我来说下自己的体会:
类不是抽象的, 类是具体的!
类是. class 字节码文件, 要想获取一个 Class 实例对象, 首先需要获取. class 字节码文件!
然后调用 Class 对象的一些方法, 进行动态获取信息以及动态调用对象方法!
二: 类类型
新建一个 Foo 类. Foo 这个类也是实例对象, 是 Class 的实例对象.
不知道你是否在意过类的声明与方法的声明:
publicclassFoo{ Foo(){// 构造方法}}publicFoomethod(){//...}
我们知道 public 后跟返回类型, 也就可以知道 class 也是一个类型.
如何表示 Class 的实例对象?
publicstaticvoidmain(String[] args) {//Foo 的实例对象, new 就出来了 Foo foo1 =newFoo();// 如何表示?// 第一种: 告诉我们任何一个类都有一个隐含的静态成员变量 classClassc1 = Foo.class;// 第二种: 已经知道该类的对象通过 getClass 方法 Classc2 = foo1.getClass(); System.out.println(c1 == c2);// 第三种: 动态加载 Classc3 =null;try{ c3 =Class.forName("cn.zyzpp.reflect.Foo"); }catch(ClassNotFoundException e) { e.printStackTrace(); } System.out.println(c2 == c3); }
上述打印结果全是 true
尽管 c1 或 c2 都代表了 Foo 的类类型, 一个类只能是 Class 类的一个实例变量.
我们完全可以通过类的类类型 (Class 类型) 创建类的实例对象.
// 此时 c1 c2 c3 为 Class 的实例对象 try{// Foo foo = (Foo)c1.newInstance(); Foo foo = (Foo)c3.newInstance(); foo.print(); }catch(InstantiationException e) { e.printStackTrace(); }catch(IllegalAccessException e) { e.printStackTrace(); }
静态加载
new 创建对象是静态加载类, 在编译时刻就需要加载所有的可能使用到的类 .
动态加载
使用 Class.forName("类的全称") 加载类称作为动态加载 .
编译时刻加载类是静态加载类, 运行时刻加载类是动态加载类.
举个例子
定义 Office 类
publicclassOffice{publicvoidprint(){ System.out.println("office"); }}
定义 Loading 类
publicclassLoading {publicstaticvoidmain(String[] args) {try{// 在运行时再动态加载类 //arg[0] 为 java 执行命令时传的参数 Class a =Class.forName(args[0]); Office office = (Office) a.newInstance(); office.print(); }catch(ClassNotFoundException e) { e.printStackTrace(); }catch(IllegalAccessException e) { e.printStackTrace(); }catch(InstantiationException e) { e.printStackTrace(); } }}
执行过程
D:\>javac -encoding utf-8Loading.java Office.javaD:\>java Loading Officeoffice
通过 Class a=Class.forName(arg[0]) 动态加载获取类, 因编译时不知道使用哪个类, 因此编译没有加载任何类, 直接通过编译, 运行时, 根据 java Loading Office (office 是一个类类型 / 类, 下标 arg[0]), 去确定 a 是哪个类. 这就是动态加载. 如果 Office 类不存在, 此时运行会报错. 这就是为何有时候会出现编译通过, 运行报错的原因.
这里需要有一个具体的概念, 类就是. class 字节码文件的实例对象!
动态加载一个好处, 就是可以随时增加需要编译的类. 例如把 Office 改造为抽象类或接口, 定义不同的子类, 动态选择加载.
三: 类的反射
通过上面的三种方法获取到类的类类型, 就可以获取到该类的成员方法, 成员变量, 方法参数注释等信息.
方法对象是 Method 类, 一个成员方法就是一个 Method 对象.
方法解释
getMethods()返回该类继承以及自身声明的所有 public 的方法数组
getDeclaredMethods()返回该类自身声明的所有 public 的方法数组, 不包括继承而来
成员变量也是对象, 是 java.lang.reflect.Field 对象, Field 类封装了关于成员变量的操作.
方法解释
getFields()获取所有的 public 的成员变量信息, 包括继承的.
getDeclaredFields()获取该类自己声明的成员变量信息, public,private 等
获取 Java 语言修饰符 (public,private,final,static) 的 int 返回值, 再调用 Modifier.toString() 获取修饰符的字符串形式, 注意该方法会返回所有修饰符.
方法解释
getModifiers()以整数形式返回由此对象表示的字段的 Java 语言修饰符.
获取注释
方法解释
getAnnotations()返回此元素上存在的所有注释.
getDeclaredAnnotations()返回直接存在于此元素上的所有注释.
构造函数也是对象, 是 java.lang.reflect.Constructor 的对象.
方法解释
getConstructors()返回所有 public 构造方法
getDeclaredConstructors()返回类的所有构造方法, 不止 public
完整示例
privatevoid printClassMessage(Object obj){// 要获取类的信息, 首先获取类的类类型 Class clazz = obj.getClass();// 获取类的名称 System.out.println(Modifier.toString(clazz.getModifiers())+""+ clazz.getClass().getName()+" "+clazz.getName()+"{"); System.out.println("---- 构造方法 ----");// 构造方法 Constructor[] constructors = clazz.getDeclaredConstructors();for(Constructorconstructor: constructors){// 构造方法修饰符与名字 System.out.print(Modifier.toString(constructor.getModifiers())+" "+constructor.getName()+"(");// 构造函数的所有参数类型 Class[] parameterTypes =constructor.getParameterTypes();for(Class c: parameterTypes){ System.out.print(c.getName()+", "); } System.out.println("){}"); } System.out.println("---- 成员变量 ----");// 成员变量 Field[] fields = clazz.getDeclaredFields();for(Field field: fields){ System.out.println(" "+Modifier.toString(field.getModifiers())+" "+field.getType().getName() +" "+ field.getName()+";"); } System.out.println("---- 成员方法 ----");//Method 类, 方法对象, 一个成员方法就是一个 Method 对象 Method[] methods = clazz.getDeclaredMethods();for(Method method : methods){// 获取方法返回类型 Class returnType = method.getReturnType();// 获取方法上的所有注释 Annotation[] annotations = method.getAnnotations();for(Annotationannotation: annotations){// 打印注释类型 System.out.println(" @"+annotation.annotationType().getName()+" "); }// 打印方法声明 System.out.print(" "+Modifier.toString(returnType.getModifiers())+" "+returnType.getName()+" "+method.getName()+"(");// 获取方法的所有参数类型 Class[] parameterTypes = method.getParameterTypes();// 获取方法的所有参数 Parameter[] parameters = method.getParameters();for(Parameter parameter: parameters){// 参数的类型, 形参(全是 arg123..)System.out.print(parameter.getType().getName()+" "+parameter.getName()+", "); } System.out.println(")"); } System.out.println("}"); }
以 String 对象为例, 打印结果:
publicfinaljava.lang.Class java.lang.String{---- 构造方法 ----publicjava.lang.String([B,int,int, ){} java.lang.String([C,boolean, ){}---- 成员变量 ----privatefinal[C value;privateinthash;---- 成员方法 ----@java.lang.DeprecatedpublicabstractfinalvoidgetBytes(intarg0,intarg1, [B arg2,intarg3, )......}
欢迎大家加入 Java 进阶高级架构 / 互联网: 855355016 本群提供免费的学习指导 架构资料 以及免费的解答不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导进群修改群备注: 开发年限 - 地区 - 经验方便架构师解答问题
四: 方法的反射
定义了一个类 Foo 用于测试
publicclassFoo{ publicvoidprint(Stringname,intnum) { System.out.println("I am"+name+"age"+num); }}
目标: 通过反射获取该方法, 传入参数, 执行该方法!
1. 获取类的方法就是获取类的信息, 获取类的信息首先要获取类的类类型
Classclazz = Foo.class;
2. 通过名称 + 参数类型获取方法对象
Method method = clazz.getMethod("print",newClass[]{String.class,int.class});
3. 方法的反射操作是通过方法对象来调用该方法, 达到和 new Foo().print()一样的效果
方法若无返回值则返回 null
Objecto = method.invoke(newFoo(),newObject[]{"name",20});
五: 通过反射认识泛型
publicstaticvoidmain(String[] args) { ArrayList stringArrayList =newArrayList<>(); stringArrayList.add("hello"); ArrayList arrayList =newArrayList();Classc1 = stringArrayList.getClass();Classc2 = arrayList.getClass(); System.out.println(c1 == c2); }
打印结果为 true
c1==c2 的结果返回说明编译之后集合的泛型是去泛型化的. 换句话说, 泛型不同, 对类型没有影响.
Java 中集合的泛型其实只是为了防止错误输入, 只在编译阶段有效, 绕过编译就无效.
验证
我们可以通过反射来操作, 绕过编译.
publicstaticvoidmain(String[] args) { ArrayList stringArrayList =newArrayList<>(); stringArrayList.add("hello"); ArrayList arrayList =newArrayList();Classc1 = stringArrayList.getClass();Classc2 = arrayList.getClass(); System.out.println(c1 == c2);try{ Method method = c1.getMethod("add",Object.class); method.invoke(stringArrayList,20); System.out.println(stringArrayList.toString()); }catch(NoSuchMethodException e) { e.printStackTrace(); }catch(IllegalAccessException e) { e.printStackTrace(); }catch(InvocationTargetException e) { e.printStackTrace(); } }
打印结果:
true[hello,20]
来源: http://www.jianshu.com/p/d0a6daec6076