“JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。”这是百度百科对JAVA反射的描述,仅凭这句话是没法明白反射的真正含义,所以还需要深入剖析。正如其描述中所讲,反射机制一般体现在运行状态,那么什么是运行状态?这就要追溯到JAVA程序的运行过程。
一、java程序的运行过程:
JAVA程序运行过程分两个阶段:
1、编译阶段:将.java的源文件编译为.class字节码文件;
java中的每一个类都会被编译为一个.class的字节码文件,如果这个类有依赖其他的类,那么首先会检查并编译依赖类,如果找不到依赖类,编译时就会报错。
2、运行阶段:jvm将.class字节码翻译成机器码,然后在操作系统上运行;
运行过程分为类加载和类执行两个阶段:
1⃣️类加载:类加载的过程就是将类的class文件装入内存,并为之创建一个java.lang.Class对象,该对象包含类的成员变量、成员方法、构造方法等所有信息;jvm只有在需要某个类的时候才会加载它,不会在事先将所有的类都加载进来,毕竟加载类是一个消耗内存的事情,如果把有的没的都加载进来,势必降低性能。
2⃣️类执行:类执行的时候,jvm会根据加载时创建的类型Class对象,找到main函数,即程序的入口,从而开始执行;
以上就是一个java 程序执行的基本过程,可参考下面两片博文:
java程序编译和运行过程:https://www.cnblogs.com/qiumingcheng/p/5398610.html
java中类加载和反射技术实例:https://www.cnblogs.com/jiaqingshareing/p/6024541.html
理解了java程序的运行过程之后,也就解决了上述问题,即所谓的运行状态就是类执行阶段的状态,因为这个阶段类的class文件已经加入内存,java.lang.Class对象已被创建,我们就能用这个对象获取的类相关属性、方法等信息。根据以上描述,可以总结出java中给一个类创建实例对象的两种方式:
1、使用new关键字:这种方式是正向的,我们明确知道这个类的所有属性方法,直接new一个对象;
2、通过类的Class对象创建:如果我们要创建某个类的对象实例,但是现在除了这个类的Class对象之外对其一无所知,这时我们就可以借助Class对象反向地创建实例,这就引出了反射的概念。
二、反射的理解
1、对反射的一点理解:看了这么多资料,按照我的理解,反射就是通过某个类编译之后的Class对象获取这个类的实例对象信息。我们现在常用的编辑器,如eclipse、idea等,都有一个代码提示功能,在开发过程中很多类的属性、方法等都是借助这个功能获取和使用的,试想如果没有这个功能,我们怎么知道使用哪个方法?不排除有人不需要借助代码提示就能知道所有类库的中API接口,但是我想对于大部分人还是有困难的,尤其在第一次使用一些第三方库的时候,此时如果没有代码提示,我们就可以使用反射的方式获取某个类中的属性、方法等。我不知道编辑器的代码提示原理是不是基于反射的,但是反射的功能就跟这个差不多,反向推到。(以上内容纯属自己的理解,不可轻信,如有不当之处,欢迎指正)
2、反射的功能
反射的功能有构建对象、反射属性、方法等,下面介绍常用的这三种。第一部分已经说明反射必须要借助类的Class对象,所以在介绍其功能之前首先了解一下获取Class对象的方式:
1⃣️getClass()方法:该方法仅限于已知某个类实例的情况,且该类为引用类型;
2⃣️Class属性:该方法适用于所有类型,包括基本数据类型,对于基本类型的封装类,还可以使用.TYPE方法获取其基本类型的Class实例,如:Integer.TYPE=int.Class;
3⃣️Class.forName()方法:该方法针对已知类的全路径的情况,且该类仅限于引用类型;
以上就是三种获取类的Class实例对象的方式。下面开始介绍反射的功能。
三、反射的常用功能
1、构建对象
前面已经说过,创建类的实例时,除了使用new关键字之外,还可以使用反射的方式。在使用反射时,需要区分构造函数是否含参。
1⃣️构造函数无参数:这种情况比较简单,相当于直接new的时候不需要传任何参数的情况,代码如下:
1 /** 2 * 创建一个普通类,通过反射创建其实例对象(无参数) 3 */ 4 public class ReflectServiceImpl { 5 public void sayHello(String name) { 6 System.out.println("Hello " + name); 7 } 8 }
1 public class ReflectServiceImplTest { 2 /** 3 * 通过反射方式创建实例对象 4 * @return 5 */ 6 public ReflectServiceImpl getIntance() { 7 ReflectServiceImpl object = null; 8 try { 9 //1、通过类全名的方式获取Class对象; 10 //2、构造函数不含参数时直接调用Class对象的newInstance()方法; 11 object = (ReflectServiceImpl) Class.forName("com.daily.reflect.ReflectServiceImpl").newInstance(); 12 } catch (Exception e) { 13 e.printStackTrace(); 14 } 15 16 return object; 17 } 18 19 public static void main(String[] args) { 20 ReflectServiceImpl object = new ReflectServiceImplTest().getIntance(); 21 object.sayHello("World!");//执行结果:Hello World! 22 } 23 }
2⃣️构造函数有参数:这种情况需要通过构造函数创建实例对象
1 /** 2 * 构造函数包含参数的类 3 */ 4 public class ReflectServiceImpl2 { 5 public String name; 6 public int age; 7 8 public ReflectServiceImpl2(String name, int age) { 9 this.name = name; 10 this.age = age; 11 } 12 13 public void selfIntroduce() { 14 System.out.println("My name is " + name + " and is am " + age + " years old."); 15 }
1 /** 2 * @author hyc 3 * 通过反射构建含参类的实例对象 4 */ 5 public class ReflectServiceImpl2Test { 6 7 public ReflectServiceImpl2 getIntance() { 8 ReflectServiceImpl2 object = null; 9 try { 10 //1、通过.class属性的方式获取Class实例; 11 //2、需要先获取构造函数再构建实例,构造函数传入参数类型(其实时参数类型的Class对象),构建实例时传入参数值; 12 object = ReflectServiceImpl2.class.getConstructor(String.class,int.class).newInstance("zhangsan",20); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 return object; 17 } 18 19 public static void main(String[] args) { 20 ReflectServiceImpl2 ob2 = new ReflectServiceImpl2Test().getIntance(); 21 ob2.selfIntroduce();//运行结果:My name is zhangsan and is am 20 years old. 22 } 23 }
2、反射属性、方法
反射属性、方法时有两种系列方法,一种是get***,一种是getDeclared***,这两种系列的区别是:前者获取的是所有“公有的”属性或方法,包括当前类继承的和所实现接口中的;而后者获取的是“当前类中声明的”属性或方法,不受访问权限的影响。为了区分这两种方法,先创建一个父类、一个接口和一个子类,子类继承父类并实现接口,父类和接口中都定义公共变量和成员方法,父类中还有含参构造方法。
1⃣️父类
1 public class ReflectServceImpl1Father { 2 public String sex; //性别 3 public String address; //住址 4 public int height; //身高 5 public int weight; //体重 6 7 //父类中如果没有无参构造函数而含有有参构造函数,子类中必须使用super关键字调用含参构造函数 8 public ReflectServceImpl1Father(String sex, String address, int height, int weight) { 9 this.sex = sex; 10 this.address = address; 11 this.height = height; 12 this.weight = weight; 13 } 14 15 public void getBaseInfo() { 16 System.out.println("性别:"+ sex + ";家庭住址:" + address + ";身高:" + height + ";体重:" + height); 17 } 18 }
2⃣️接口
1 /** 2 * @author hyc 3 * 声明一个接口,定义三个成员变量 4 */ 5 public interface ReflectServiceImpl1Interface { 6 public static final String weiboAccount = "csu.weibo"; 7 public static final String zhihuAccount = "csu.zhihu"; 8 public static final String weixinAccout = "csu.weixin"; 9 10 public void login(); //登陆 11 12 public void register(); //注册 13 14 public void logout(); //登出 15 }
3⃣️子类(继承父类,实现接口,并声明成员变量、成员函数和含参构造方法)
1 /** 2 * @author hyc 3 * 创建一个有参数的类,通过反射创建其实例对象 4 */ 5 public class ReflectServiceImpl1 extends ReflectServceImpl1Father implements ReflectServiceImpl1Interface{ 6 /** 7 * 声明三个成员变量,两个公有一个私有 8 */ 9 public String name; 10 public String job; 11 private int age; 12 13 /** 14 * 声明三个含参构造函数 15 * @param name 16 */ 17 public ReflectServiceImpl1(String name) { 18 super("woman","beijing",165,100); 19 this.name = name; 20 } 21 22 public ReflectServiceImpl1(String name,String job) { 23 super("woman","beijing",165,100); 24 this.name = name; 25 this.job = job; 26 } 27 28 protected ReflectServiceImpl1(String name,String job,int age) { 29 super("woman","beijing",165,100); 30 this.name = name; 31 this.job = job; 32 this.age = age; 33 } 34 35 /** 36 * 在子类中定义三个成员函数 37 */ 38 public void sayHello() { 39 System.out.println("Hello " + name); 40 } 41 42 public void sayHi() { 43 System.out.println("Hello " + name + " ,my job is a " + job); 44 } 45 46 public void sayHey() { 47 System.out.println("Hello " + name +" ,i am " + age +" years old and my job is a " + job); 48 } 49 50 /** 51 * 重写接口中的三个方法 52 */ 53 public void login() { 54 System.out.println("用户登陆"); 55 } 56 57 public void register() { 58 System.out.println("用户注册"); 59 } 60 61 public void logout() { 62 System.out.println("用户登出"); 63 } 64 }
以上是基础代码,下面开始反射。
1⃣️反射构造函数
1 /** 2 * 获取类中的构造函数 3 */ 4 public static void getContructors() { 5 try { 6 // 返回类中声明的公共构造函数 7 Constructor<?>[] publicCons = Class.forName("com.daily.reflect.ReflectServiceImpl1").getConstructors(); 8 System.out.println("所有构造方法的个数:" + publicCons.length); 9 for (Constructor<?> constructor : publicCons) { 10 System.out.println(constructor.getName()); 11 } 12 // 返回类中声明的所有构造函数:不受访问权限的影响 13 Constructor<?>[] declaredCons = Class.forName("com.daily.reflect.ReflectServiceImpl1") 14 .getDeclaredConstructors(); 15 System.out.println("声明的构造方法的个数:" + declaredCons.length); 16 for (Constructor<?> constructor : declaredCons) { 17 System.out.println(constructor.getName()); 18 } 19 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 }
测试输出结果:
1 public static void main(String[] args) { 2 ReflectServiceImpl1MethodTest.getContructors(); 3 }
输出结果:
1 所有构造方法的个数:2 2 com.daily.reflect.ReflectServiceImpl1 3 com.daily.reflect.ReflectServiceImpl1 4 声明的构造方法的个数:3 5 com.daily.reflect.ReflectServiceImpl1 6 com.daily.reflect.ReflectServiceImpl1 7 com.daily.reflect.ReflectServiceImpl1
从上面的结果来看,通过getConstructors获取的构造函数只有两个,而通过getDeclaredConstructors方法获取的构造函数有三个,这是因为前者只获取公共构造函数,但不包括父类中的,而后者则获取的是当前类中声明的所有构造函数,且不受访问权限的影响。因为其中一个构造函数是用protected关键字修饰,所以通过前者无法获取。
2⃣️反射成员变量
1 /** 2 * 反射成员变量 3 */ 4 public static void getFields() { 5 try { 6 // 返回公共的成员变量:包括继承类以及所实现接口中的 7 Field[] fields = Class.forName("com.daily.reflect.ReflectServiceImpl1").getFields(); 8 System.out.println("成员变量的个数:" + fields.length); 9 for (Field field : fields) { 10 System.out.println(field.getName()); 11 } 12 // 返回本类中声明的成员变量:不包括所实现接口中的和继承类中的,且访问权限不受限制 13 Field[] declaredFields = Class.forName("com.daily.reflect.ReflectServiceImpl1").getDeclaredFields(); 14 System.out.println("类中声明的成员变量的个数:" + declaredFields.length); 15 for (Field field : declaredFields) { 16 System.out.println(field.getName()); 17 } 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 22 }
测试反射结果:
1 public static void main(String[] args) { 2 ReflectServiceImpl1MethodTest.getFields(); 3 }
反射结果:
1 成员变量的个数:9 2 name 3 job 4 weiboAccount 5 zhihuAccount 6 weixinAccout 7 sex 8 address 9 height 10 weight 11 类中声明的成员变量的个数:3 12 name 13 job 14 age
从上面的结果来看,通过getFields方法获取的成员变量有9个,分别是父类中的4个,接口中的3个和当前类中的2个;而通过getDeclaredFields方法获取的成员变量只有3个,都是当前类中的,这就证明前者获取的是所有公共的成员变量,包括它所继承的父类和所实现接口中的,而后者则获取的是当前类中声明的成员变量,不受访问权限的影响。
3⃣️反射成员方法
1 /** 2 * 获取成员方法 3 */ 4 public static void getMethods() { 5 try { 6 // 获取所有公共的成员函数:包括所实现接口以及继承的父类中的成员函数 7 Method[] methods = Class.forName("com.daily.reflect.ReflectServiceImpl1").getMethods(); 8 System.out.println("所有成员方法的个数:" + methods.length); 9 for (Method method : methods) { 10 System.out.println(method.getName()); 11 } 12 13 // 获取本类中所有声明的成员函数:包括所实现接口中的,但不包括所继承父类中的,访问权限不受限制 14 Method[] declaredMethods = Class.forName("com.daily.reflect.ReflectServiceImpl1").getDeclaredMethods(); 15 System.out.println("所有在类中声明的成员方法的个数:" + declaredMethods.length); 16 for (Method method : declaredMethods) { 17 System.out.println(method.getName()); 18 } 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 }
测试反射结果:
1 public static void main(String[] args) { 2 ReflectServiceImpl1MethodTest.getMethods(); 3 }
查看反射结果:
1 所有成员方法的个数:16 2 register 3 sayHello 4 sayHey 5 logout 6 login 7 sayHi 8 getBaseInfo 9 wait 10 wait 11 wait 12 equals 13 toString 14 hashCode 15 getClass 16 notify 17 notifyAll 18 所有在类中声明的成员方法的个数:6 19 register 20 sayHello 21 sayHey 22 logout 23 login 24 sayHi
从上面的结果来看,通过getMethods获取的方法有16个,除了子类中的3个,父类中的1个和接口中的3个之外,还有9个是Object超类中的方法,因为所有的类都会继承Object类,也就能获取其成员方法;而通过getDeclaredMethods获取的方法只有子类中的3个和所实现接口中的3个,如果重写父类中的方法,也能获取到,所以可以看出前者是获取所有公共方法,包括当前类所继承的父类及所实现接口中的,而后者只是获取当前类中的成员函数,且不受访问权限的限制。
3、反射方法
反射还有一个功能就是反射方法,通过这个功能我们可以在调用某个方法之前判断它是否存在,不存在是进行人性化处理,以防运行是抛出异常影响用户体验。反射方法时,首先依然要获取Class对象,然后利用该对象通过方法名获取Method对象,然后调用Method对象到invoke()方法将其挂在某个对象下面进行调用。所以反射方法需要分三步
1⃣️获取Class对象。在获取Class对象之前还是要进行构建对象实例,这个前面已经介绍,这里不再赘述。
2⃣️获取Method对象。Method对象是通过Class对象获取的,如果某个方法含有参数,则需要传参数类型,否则只需传方法名称。
3⃣️调用invoke方法,将方法挂在某个对象下,这样就能通过对象调用方法。调用该方法时,如果反射的方法有参数,则需要传参数值,否则不需要。
下面以反射上述子类中的sayHey()方法为例,来看代码:
1 /** 2 * 通过反射创建对象实例:包含三个参数的构造方法 3 * 4 * @param args 5 */ 6 public static void reflectMethodHey() { 7 ReflectServiceImpl1 object = null; 8 try { 9 //通过类全名的方式反射出对象实例:因为需要反射的方法中有三个参数,所以使用三个参数的构造函数初始化变量 10 object = (ReflectServiceImpl1) Class.forName("com.daily.reflect.ReflectServiceImpl1") 11 .getDeclaredConstructor(String.class, String.class,int.class).newInstance("hyc", "programmer",25); 12 //获取Class对象并反射方法:因为sayHey方法中没有参数,所以此处无需参数类型 13 Method method = object.getClass().getMethod("sayHey"); 14 //使用invoke方法调用方法,因为sayHey方法中没有参数,所以此处无需参数值 15 method.invoke(object); 16 } catch (Exception e) { 17 e.printStackTrace(); 18 } 19 }
测试调用结果:
1 public static void main(String[] args) { 2 ReflectServiceImpl1MethodTest.reflectMethodHey(); 3 }
查看结果:
1 Hello hyc ,i am 25 years old and my job is a programmer
由此可见,方法调用成功,所以反射成功。
在日常开发中,同一个系统会有不同的版本进行维护,不同版本之间可能存在个别后台接口不一致的差异,此时如果有些方法在低版本中不存在而在高版本中存在,这样在做版本兼容的时候,可以通过反射的方式判断某个方式是否存在,存在则为高版本,否则为低版本,需进行特殊处理,以免运行是报错。
以上就是反射中一些比较重要的知识点总结,原创内容,如果有不对的地方,欢迎指正。