• 浅谈java反射机制


    目录

    什么是反射

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

    初探

    对反射的最初接触是学习jdbc时,加载数据库驱动时会这样写:Class.forName("com.mysql.jdbc.Driver"),当时似懂非懂的也不知道是什么意思,随着自己的不断学习,越来越感觉反射的神奇,让我们一起来揭开它的神秘面纱吧。

    学习一个知识,自然是最先从api开始,反射涉及的类,除了Class类之外,基本上都在java.lang.reflect包里面,常用的类有Constructor,Field,Method类等,AccessibleObject类是前面三个类的基类,主要包含设置安全性检查等方法,下面,我们看一下reflect包的结构

    可以看出,涉及的类并不多,让我一起来看一下其中比较常用的类的用法吧 ## 初始化 测试用例采用junit+log4j,新建一个test类,一个javabean

    其中name属性get,set方法用private修饰

    User类

    package com.test;
    
    public class User {
      private String name = "init";
      private int age;
      public User() {}
      public User(String name, int age) {
        super();
        this.name = name;
        this.age = age;
      }
      private String getName() {
        return name;
      }
      private void setName(String name) {
        this.name = name;
      }
      public int getAge() {
        return age;
      }
      public void setAge(int age) {
        this.age = age;
      }
      @Override
      public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
      }
    }
    

    Test类

    public class ReflectTest {
      private static Logger logger = Logger.getLogger(ReflectTest.class);
      private static Class<User> userClass = User.class;
    }
    

    在类加载的时候,jvm会创建一个class对象

    class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种

    1. 根据类名:类名.class
    2. 根据对象:对象.getClass()
    3. 根据全限定类名:Class.forName(全限定类名)
      @Test
      public void classTest() throws Exception {
        // 获取Class对象的三种方式
        logger.info("根据类名:  	" + User.class);
        logger.info("根据对象:  	" + new User().getClass());
        logger.info("根据全限定类名:	" + Class.forName("com.test.User"));
        // 常用的方法
        logger.info("获取全限定类名:	" + userClass.getName());
        logger.info("获取类名:	" + userClass.getSimpleName());
        logger.info("实例化:	" + userClass.newInstance());
      }
    

    console

    根据类名:  	class com.test.User
    根据对象:  	class com.test.User
    根据全限定类名:	class com.test.User
    获取全限定类名:	com.test.User
    获取类名:	com.test.User
    实例化:	User [name=init, age=0]
    

    构造函数

    构造函数是java创建对象的必经之路,所以通过反射拿到一个类的构造函数后,再去创建这个类的对象自然是易如反掌,常用的方法如下:

      @Test
      public void constructorTest() throws Exception {
        // 获取全部的构造函数
        Constructor<?>[] constructors = userClass.getConstructors();
        // 取消安全性检查,设置后才可以使用private修饰的构造函数,也可以单独对某个构造函数进行设置
        // Constructor.setAccessible(constructors, true);
        for (int i = 0; i < constructors.length; i++) {
          Class<?> parameterTypesClass[] = constructors[i].getParameterTypes();
          System.out.print("第" + i + "个构造函数:	 (");
          for (int j = 0; j < parameterTypesClass.length; j++) {
            System.out.print(parameterTypesClass[j].getName() + (j == parameterTypesClass.length - 1 ? "" : "	"));
          }
          logger.info(")");
        }
        // 调用构造函数,实例化对象
        logger.info("实例化,调用无参构造:	" + constructors[0].newInstance());
        logger.info("实例化,调用有参构造:	" + constructors[1].newInstance("韦德", 35));
      }
    

    console

    第0个构造函数:	 ()
    第1个构造函数:	 (java.lang.String	int)
    实例化,调用无参构造:	User [name=init, age=0]
    实例化,调用有参构造:	User [name=韦德, age=35]
    

    属性

    犹记得学习spring ioc之时,对未提供set方法的private属性依然可以注入感到神奇万分,现在看来,这神奇的根源自然是来自于java的反射,常用的方法如下:

      @Test
      public void fieldTest() throws Exception {
        User user = userClass.newInstance();
        // 获取当前类所有属性
        Field fields[] = userClass.getDeclaredFields();
        // 获取公有属性(包括父类)
        // Field fields[] = cl.getFields();
        // 取消安全性检查,设置后才可以获取或者修改private修饰的属性,也可以单独对某个属性进行设置
        Field.setAccessible(fields, true);
        for (Field field : fields) {
          // 获取属性名 属性值 属性类型
          logger.info("属性名:" + field.getName() + "	属性值:" + field.get(user) + "  	属性类型:" + field.getType());
        }
        Field fieldUserName = userClass.getDeclaredField("name");
        // 取消安全性检查,设置后才可以获取或者修改private修饰的属性,也可以批量对所有属性进行设置
        fieldUserName.setAccessible(true);
        fieldUserName.set(user, "韦德");
        // 可以直接对 private 的属性赋值
        logger.info("修改属性后对象:	" + user);
      }
    

    console

    属性名:name	属性值:init  	属性类型:class java.lang.String
    属性名:age	属性值:0  	属性类型:int
    修改属性后对象:	User [name=韦德, age=0]
    

    方法

    大家对javabean肯定不会陌生,在用框架操作javabean时,大多都是通过反射调用get,set方法Javabean进行操作,常用的方法如下:

      @Test
      public void methodTest() throws Exception {
        User user = userClass.newInstance();
        // 获取当前类的所有方法
        Method[] methods = userClass.getDeclaredMethods();
        // 获取公有方法(包括父类)
        // Method[] methods = userClass.getMethods();
        // 取消安全性检查,设置后才可以调用private修饰的方法,也可以单独对某个方法进行设置
        Method.setAccessible(methods, true);
        for (Method method : methods) {
          // 获取方法名和返回类型 获取参数类型:getParameterTypes
          logger.info("方法名:" + method.getName() + " 	返回类型:" + method.getReturnType().getName());
        }
        // 获取无参方法
        Method getMethod = userClass.getDeclaredMethod("getName");
        // 取消安全性检查,设置后才可以调用private修饰的方法,也可以批量对所有方法进行设置
        getMethod.setAccessible(true);
        // 调用无参方法
        logger.info("调用getName方法:" + getMethod.invoke(user));
        // 获取有参方法
        Method setMethod = userClass.getDeclaredMethod("setName", String.class);
        // 取消安全性检查,设置后才可以调用private修饰的方法,也可以批量对所有方法进行设置
        setMethod.setAccessible(true);
        // 调用有参方法
        logger.info("调用setName方法:" + setMethod.invoke(user, "韦德"));
        logger.info("通过set方法修改属性后对象:	" + user);
      }
    

    console

    方法名:toString 	返回类型:java.lang.String
    方法名:setAge 	返回类型:void
    方法名:getAge 	返回类型:int
    方法名:getName 	返回类型:java.lang.String
    方法名:setName 	返回类型:void
    调用getName方法:init
    调用setName方法:null
    通过set方法修改属性后对象:	User [name=韦德, age=0]
    

    完整的 源码 https://github.com/zhaoguhong/blogsrc

    总结

    不难看出,Java反射中的构造函数,属性,方法有着诸多相似之处,不仅仅是因为它们有着共同的父类AccessibleObject,基本上所有的api都有相似之处。学习的过程中死记api是最愚蠢的,找方法,理解反射的设计思路。去尝试感悟设计思想,才是王道。

    上面只是对反射的常用方法提供了示例,最好的学习方法自然是参照api,自己去实践。纸上得来终觉浅,绝知此事要躬行。通过自己的不断练习,体会,思考,达到融会贯通的目的。

    思考

    java以面向对象和封装性著称,但反射在java中堪称作弊器,似乎无所不能,给人一种建了一道围墙,下面又留了一道门的感觉,是否破坏了程序的封装性?

    笔者认为:循规蹈矩固然好,但过于注重规范反而影响程序的灵活性。Java反射给我们带了灵活性的同时,极大的方便了我们的编程,而且反射堪称各大框架的基础。如此看来,显然利大于弊,你怎么看?

  • 相关阅读:
    python 并发编程 多线程 event
    python 并发编程 多线程 定时器
    python 并发编程 多线程 信号量
    linux top 查看CPU命令
    python 并发编程 多线程 GIL与多线程
    python 并发编程 多线程 死锁现象与递归锁
    python 并发编程 多线程 GIL与Lock
    python GIL全局解释器锁与互斥锁 目录
    python 并发编程 多线程 GIL全局解释器锁基本概念
    执行python程序 出现三部曲
  • 原文地址:https://www.cnblogs.com/zhaoguhong/p/6937364.html
Copyright © 2020-2023  润新知