Java内功修炼系列一反射

“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

 由此可见,方法调用成功,所以反射成功。

在日常开发中,同一个系统会有不同的版本进行维护,不同版本之间可能存在个别后台接口不一致的差异,此时如果有些方法在低版本中不存在而在高版本中存在,这样在做版本兼容的时候,可以通过反射的方式判断某个方式是否存在,存在则为高版本,否则为低版本,需进行特殊处理,以免运行是报错。

以上就是反射中一些比较重要的知识点总结,原创内容,如果有不对的地方,欢迎指正。

原文地址:https://www.cnblogs.com/hellowhy/p/9598497.html