zoukankan      html  css  js  c++  java
  • 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

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

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

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

  • 相关阅读:
    C#后台调用Delphi 的Ocx
    打包.NET程序
    Tomcat配置虚拟目录、多域名、多个Http监听端口的方式
    检测多个Jar包冲突的class
    SessionFactoryImpl.get错误:java.lang.ArrayIndexOutOfBoundsException: 68问题
    搭建内网搜索平台
    cvs update报错
    混淆和加密.NET开发工具
    CVS报错:could not find desired version问题的解决
    记录一次接口压力测试结果
  • 原文地址:https://www.cnblogs.com/hellowhy/p/9598497.html
Copyright © 2011-2022 走看看