Java的反射

1. 反射机制概述

① 反射机制的含义

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

② 反射机制的作用

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的方法

③ 反射机制的优缺点

优点:

  • 可以动态的创建对象和编译,最大限度发挥了java的灵活性。
  • 特别是在J2EE的开发中,它的灵活性就表现的十分明显。比如,一个大型的软件,总会有不同的版本进行发布,每次新版本的发布不可能要求用户把旧版本卸载,再重新安装新版本。如果采用反射机制,它就可以不用卸载只需要在运行时才动态的创建和编译

缺点:

  • 反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多

④ 反射机制的应用

  • springIOC机制底层用的就是反射技术来构建用户提供的bean实例。
  • jdbc为各个厂商提供了驱动规范,各个厂商分别有自己对应的实现,jdbc就是通过用户使用不同类型的驱动类去构建驱动对象的,初始化的时候,通过反射去调用驱动对象的各个方法
  • jdbc中有一行代码:Class.forName('com.mysql.jdbc.Driver'),那个时候只知道加载驱动,这就是反射。

2. Class类

① 反射和Class类共同提供反射支持

  • Classjava.lang.reflect 一起对反射提供了支持。

② Class类

  • Class是一个类,封装了当前对象所对应的类的信息,描述方法的 Method,描述字段的 Filed,描述构造器的 Constructor 等属性.
  • Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性、方法、构造器、实现了哪些接口等等。
  • 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
  • Class 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例

③ 获取Class对象的三种方式

  • 通过类名获取:Object.class

  • 通过对象获取:obj.getClass()

  • 通过类的完全限定名获取:Class.forName(String className)

    1.通过类名,这也告诉我们:任何一个类都有一个隐含的静态成员变量class(知道类名时用)
    clazz = Person.class;
    
    2.通过对象名,这种方式是用在传进来一个对象,却不知道对象类型的时候使用(知道对象时用)
    Object obj = new Person();
    clazz = obj.getClass();
    
    3.通过全类名(会抛出ClassNotFoundException异常),一般框架开发中这种用的比较多,
    因为配置文件中一般配的都是全类名,通过这种方式可以得到Class实例
    String className=" com.atguigu.java.fanshe.Person";
    clazz = Class.forName(className);    
    

④ 通过newInstance实例化类

  • 可以通过class对象的newInstance()方法示例化类 —— 通过class对象实例化,只能调用无参构造函数

    方法用法
    Object newInstance()调用缺省构造函数,返回该Class对象的一个实例
    1.获取Class对象
    String className="com.atguigu.java.fanshe.Person";
    Class clazz = Class.forName(className);  
    
    2.利用Class对象的newInstance方法创建一个类的实例
    Object obj =  clazz.newInstance();
    System.out.println(obj);
    

3. 反射

① 反射类

java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 创建新的对象

② 通过反射获取Field并使用的几种方法

  • 通过反射机制获取类中的属性后,可以通过f.set(obj, value)设置obj对象的属性f的值;可以通过f.get(obj)获取obj对象的属性f的值
    1.获取Class对象  
    Class stuClass = Class.forName("fanshe.field.Student");  
    
    2.获取所有的公有字段  
    Field[] fieldArray = stuClass.getFields();  
    for(Field f : fieldArray){  
        System.out.println(f.getName());  
    }  
    
    3.获取所有的字段(包括私有、受保护、默认的)
    fieldArray = stuClass.getDeclaredFields();  
    for(Field f : fieldArray){  
        System.out.println(f.getName());  
    }  
    
    4.获取公有字段,并调用
    Field f = stuClass.getField("name");  
    System.out.println(f.getName());  
    //获取一个对象  
    Object obj = stuClass.newInstance();//产生Student对象–》Student stu = new Student();  
    //为字段设置值  
    f.set(obj, "刘德华");//为Student对象中的name属性赋值–》stu.name = ”刘德华”  
    //验证  
    Student stu = (Student)obj;  
    System.out.println("验证姓名:" + stu.name);  
    
    5.获取私有字段并调用
    f = stuClass.getDeclaredField("phoneNum");  
    System.out.println(f.getName());  
    //暴力反射,解除私有限定 ,如果字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法
    f.setAccessible(true);
    f.set(obj, "18888889999");  
    System.out.println("验证电话:" + stu.getPhoneNum());  
    

③ 通过反射获取Method并使用的几种方法

  • 通过反射机制获取类中的method后,可以通过m.invoke(obj, [方法参数...])调用一个obj对象的m方法
    1.获取Class对象  
    Class stuClass = Class.forName("fanshe.method.Student");  
    
    2.获取所有公有方法
    Method[] methodArray = stuClass.getMethods();  
    for(Method m : methodArray){  
        System.out.println(m.getName());  
    }  
    
    3.获取所有的方法,包括私有的
    methodArray = stuClass.getDeclaredMethods();  
    for(Method m : methodArray){  
        System.out.println(m.getName());  
    }  
    
    4.获取指定的公有方法并使用
    Method m = stuClass.getMethod("show1", String.class);  
    //实例化一个Student对象  
    Object obj = stuClass.newInstance();  
    m.invoke(obj, "刘德华");  
      
    5.获取指定的私有方法并使用
    m = stuClass.getDeclaredMethod("show4", int.class);  
    m.setAccessible(true);//解除私有限定  
    m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(由反射获取),一个是实参  
    

④ 通过反射获取Constructor(构造器)并使用的几种方法

  • 通过反射机制获取一个类的构造方法后,可以通过c.newInstance([构造方法的参数 ... ])创建该类的一个对象
    public static void main(String[] args) throws Exception {  
       1.加载Class对象  
       Class clazz = Class.forName("fanshe.Student");  
         
       2.获取所有公有构造方法  
       Constructor[] conArray = clazz.getConstructors();  
       for(Constructor c : conArray){  
           System.out.println(c);  
       }  
         
      3. 获取所有的构造方法(包括:私有、受保护、默认、公有)
       conArray = clazz.getDeclaredConstructors();  
       for(Constructor c : conArray){  
           System.out.println(c);  
       }  
         
      4. 获取公有的无参构造方法
       Constructor con = clazz.getConstructor(null);  
       //① 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型  
       //② 返回的是描述这个无参构造函数的类对象。  
       System.out.println("con = " + con);  
       //调用构造方法  
       Object obj = con.newInstance();  
       
      5. 获取私有、带参数的构造方法,并调用 
       con = clazz.getDeclaredConstructor(char.class);  
       System.out.println(con);  
       //调用构造方法  
       con.setAccessible(true);
       obj = con.newInstance('M');  
    }  
    

⑤ 一些总结

如何创建对象实例?或如何利用反射实例化一个类?

  • 通过Class类newInstance()方法:

    Class stuClass = Class.forName("fanshe.field.Student");  
    Object obj = stuClass.newInstance();//产生Student对象–》Student stu = new Student();  
    
  • 通过Constructor类newInstance()方法:

    Class clazz = Class.forName("fanshe.Student");  
    Constructor con = clazz.getConstructor(null);  
    //调用构造方法创建对象
    Object obj = con.newInstance();  
    

4. 通过反射改变String对象的值

① 通过反射改变String对象的值

  • String的不可变的原因,见博客:Java基础(一)
  • 通过分析我们知道,要想修改String对象的值,就需要修改其value属性
  • 在实践时发现,只修改value属性,其hashCode不会同步更新。
  • 自己当时脑壳有包,想着内容都变了,hashCode也应该一起变,所以将hashCode也置零了
  • 现在想想,同一个string对象hashCode应该满足幂等性,你改变了是咋回事 😂
    public static void main(String[] args) {
        String string = "Hello, world!";
        System.out.println("value: " + string + ", hashCode: " + string.hashCode());
    
        try {
            // 获取class对象
            Class stringClass = string.getClass();
            // 获取value属性,设置为可访问
            Field valueField = stringClass.getDeclaredField("value");
            valueField.setAccessible(true);
    
            // 获取对象中value属性的值并更新
            char[] value = (char[]) valueField.get(string);
            value[7] = 'W';
            valueField.set(string, value);
    
            // 重新打印string的值和hashCode
            System.out.println("value: " + string + ", hashCode: " + string.hashCode());
    
            // 发现hashCode在value更新后,未重新计算。
            // 更新value的同时,需要将hashCode置为0,使其在获取hashCode时,重新计算
            Field hashCodeField = stringClass.getDeclaredField("hash");
            hashCodeField.setAccessible(true);
            // 直接将对象的hashCode置零
            hashCodeField.setInt(string, 0);
    
            // 重新打印,发现hashCode被重新计算
            System.out.println("value: " + string + ", hashCode: " + string.hashCode());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    

② 获取和修改成员变量的值

  • 基本类型的成员变量的值获取方法:

    byte getByte(Object obj)  //获取一个静态或实例 byte 字段的值。 
    int getInt(Object obj) //获取 int 类型或另一个通过扩展转换可以转换为 int 类型的基本类型的静态或实例字段的值。 
    short getShort(Object obj) //获取 short 类型或另一个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。 
    long getLong(Object obj) //获取 long 类型或另一个通过扩展转换可以转换为 long 类型的基本类型的静态或实例字段的值。 
    float getFloat(Object obj) //获取 float 类型或另一个通过扩展转换可以转换为 float 类型的基本类型的静态或实例字段的值。 
    double getDouble(Object obj) //获取 double 类型或另一个通过扩展转换可以转换为 double 类型的基本类型的静态或实例字段的值。 
    boolean getBoolean(Object obj) //获取一个静态或实例 boolean 字段的值。 
    char getChar(Object obj) //获取 char 类型或另一个通过扩展转换可以转换为 char 类型的基本类型的静态或实例字段的值。
    
  • 基本类型的成员变量的值修改方法:

     void setByte(Object obj, byte b) //将字段的值设置为指定对象上的一个 byte 值。 
     void setShort(Object obj, short s) //将字段的值设置为指定对象上的一个 short 值。
     void setInt(Object obj, int i) //将字段的值设置为指定对象上的一个 int 值。 
     void setLong(Object obj, long l) //将字段的值设置为指定对象上的一个 long 值。 
     void setFloat(Object obj, float f) //将字段的值设置为指定对象上的一个 float 值。
     void setDouble(Object obj, double d) //将字段的值设置为指定对象上的一个 double 值。
     void setBoolean(Object obj, boolean z) //将字段的值设置为指定对象上的一个 boolean 值。 
     void setChar(Object obj, char c) //将字段的值设置为指定对象上的一个 char 值。
    
  • 引用类型的成员变量的值获取、修改方法:

    Object get(Object obj) //返回指定对象上此 Field 表示的字段的值。 
    void set(Object obj, Object value) //将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 
    

参考链接:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值