java基础-反射

一、类的加载

(一) 定义及过程:

当程序需要使用某个类的时候,如果这个类还没有被加载到内存中,则系统会通过加载连接初始化三个步骤对这个类进行初始化;

  1. 加载:
    1. 将class文件读入内存,并为之创建一个Class对象
    2. 任何类被使用时,系统都会建立一个Class对象
  2. 连接:
    1. 验证:是否有正确的内部结构,并和其他类协调一致;
    2. 准备:负责为类的静态成员分配内存,并设置默认初始化值;(原来静态成员在这里加载)
    3. 解析:将类的二进制数据中的引用替换为直接引用;
  3. 初始化:即对对象的初始化;

(二)类初始化时机

  1. 创建类的实例;
  2. 访问类的静态变量,或者为静态变量赋值;
  3. 调用类的静态方法;
  4. 使用反射方式来强制创建某个类或者接口对应的Class对象;
  5. 初始化某个类的子类;
  6. 直接使用java.exe命令运行某个主类的时候;

(三)类加载器

  1. 作用:
    1. 负责将class文件加载到内存中,
    2. 为之生成对应的Class对象
  2. 分类
    1. 根类加载器(引导类加载器)Bootstrap ClassLoader,(即加载系统的东西)
      • 负责java核心类的加载(如String类),在jdk/jre/rt.jar文件中;
    2. 扩展类加载器Extension ClassLoader(即加载扩展类的东西)
      • 负责jre的扩展目录中jar包的加载,在jdk/jre/lib/ext目录下;
    3. 系统类加载器System ClassLoader(即加载程序员写的类)
      • 负责在JVM 启动时加载来自java命令的class文件,
      • classpath环境变量所指向的jar包类路劲

二、反射

(一) 定义、作用及特点###

  1. 作用:通过class文件来使用类的成员变量、成员方法、构造方法;
  2. 特点:运行状态下,动态获取信息以及动态调用对象方法;(因为是在运行时才做的操作,因此反射实际上有效率损失,会延长代码运行时间)
    1. 对于任何类,都可以知道这个类的所有属性和方法;
    2. 对于任何对象,都可以调用他的任意一个方法和属性;
  3. 原理:要想解剖一个类,必须先要获取一个类的字节码文件对象(即class文件)。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象;
    • 正常情况下:java文件-->class文件-->给JVM调用;
    • 反射:Class文件对象--->class文件对象-->给JVM调用;
  • 因此我们要首先获取Class文件对象,然后构造出class文件对象,这里实际上将所有的class文件看成对象,这个对象的类就是Class,成员变量就是成员变量成员方法构造方法

(二)反射的代码实现

要想使用反射,就必须首先得到class文件对象,也即Class类的对象,

因为class文件对象只有一个,所以不管通过什么方式得到Class类的对象,都是同一个对象;

1. 获取class文件对象的方式

  1. Object.getClass();

     Person p1= new Person();
     Class<? extends Person> clazz1 = p1.getClass();
     
     Person p2= new Person();
     Class clazz2 = p2.getClass();
     //true
     //因为`class文件对象`只有一个,所以不管通过什么方式得到`Class类`的对象,都是同一个对象;
     System.out.println(clazz1 == clazz2);
    
  2. 数据类型的静态属性class;

     Class p3 = Person.class;
     System.out.println(clazz1 == clazz3);
    
  3. Class类中的静态方法public static Class forName(String className)

      Class clazz4 = Class.forName("com.wvbx.java.Person");
      System.out.println(clazz1 == clazz4);     
    

2. 通过反射获取构造方法并使用

(1)常用API 说明及举例
  1. clazz.getConstructors();获取所有public修饰的构造方法

     String name = Person.class.getName();
     try {
         Class<?> clazz = Class.forName(name);
         //获取所有public修饰的构造方法
         Constructor<?>[] constructors = clazz.getConstructors();
         for (Constructor<?> constructor : constructors) {
             Log.i(TAG, "onCreate: " + constructor);
         }
     } catch (ClassNotFoundException e) {
         e.printStackTrace();
     }    
    
  2. clazz.getDeclaredConstructors();获取所有的构造方法,即使是private修饰的;

  3. clazz.getDeclaredConstructor(Class<?> paramsTypes)获取指定参数列表的构造方法;这里需要传入的是数据类型的静态属性,

     Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(int.class, String.class);
     Log.i(TAG, "onCreate: " + declaredConstructor);
    

    输出结果:

      public com.wvbx.java.Person(int,java.lang.String)
    

注意:在所有的方法里面如果有Declared这个单词,就表示可以获取任意权限的构造方法,包括private修饰的构造方法;如果不包含Declared这个单词,就只能获取public修饰的构造方法,这一点在获取成员方法,成员变量时一样适用;

(2)通过调用上述方法,我们可以得到指定参数列表的构造方法对象,通过这个构造方法对象来创建对应参数列表的对象实例;
  1. 通过调用constructor.newInstance(...)方法来创建对象,这里的参数是对被创建对象的数据初始化;

     Object newInstance = declaredConstructor.newInstance(18, "张三");
     Log.i(TAG, "onCreate: "+newInstance);
    

输出结果

    com.wvbx.java.Person@f648ccf

可以看到,虽然我们用Object类来接受,但实际上就是我们创建的Person类;因此把对象强制转换成Person类也不会报错,而且初始化成功;

    declaredConstructor.setAccessible(true);
    Person newInstance = (Person) declaredConstructor.newInstance(18, "张三");    
    Log.i(TAG, "onCreate: "+newInstance.getAge()+"---"+newInstance.getName());

输出结果

    onCreate: 18---张三

declaredConstructor.setAccessible(true);在给指定的构造方法的参数赋值时,如果构造方法是private修饰的,就会赋值失败,必须加上这一句话才会成功。赋值为true时,指示反射的对象在使用时应该取消java语言访问检查;

这样我们就可以使用newInstance这个对象的所有成员变量和成员方法了;

注意:调用clazz.getDeclaredConstructor();时,这里需要传的参数有几个,在Object newInstance = con.newInstance("张三",18,"北京");就需要传入同样个数的参数,并且对应的数据类型不能出错,这样才能反射调用成功。因为通过反射创建对象时,是通过特定的构造方法创建对象,比如通过反射三个参数的构造方法来创建对象,如果con.newInstance()一个参数也没有,就相当于通过无参构造方法创建对象,自然报错。可以如此理解:Person p = new Person("张三",18,"北京")这样一步在反射中是通过多步来完成的;

3. 通过反射获取成员变量并使用

(1)常用API 说明及举例
  1. Field[] fields = clazz.getFields();获取所有public修饰的成员变量;

  2. Field[] fields = clazz.getDeclaredFields();获取所有的成员变量,即使是private修饰的成员变量;

  3. Field fields = clazz.getDeclaredField("address");获取指定名字的成员变量的对象;

  4. fields.set(Object obj,Object value);给指定对象的该字段赋值,因此这里首先要有个对象,因为这里字段就是通过反射来获取的,因此这里的对象自然也要通过反射创建;

     String name = Person.class.getName();
     try {
         Class<?> clazz = Class.forName(name);
         Constructor<?> con = clazz.getDeclaredConstructor();
         con.setAccessible(true);
         //创建对象
         Object newInstance = con.newInstance();
         
         //获取指定字段信息
         Field fields = clazz.getDeclaredField("address");
         fields.setAccessible(true);
         //给指定对象的该字段赋值
         fields.set(newInstance, "上海");
         Log.i(TAG, "onCreate: " + newInstance);
     } catch (Exception e) {
         e.printStackTrace();
     }
    

    输出结果

     Person{address='上海', name='null', age=0}
    
注意:这里的参数类型必须和类中的规定的参数类型一样,否则会报错;

4. 通过反射获取成员方法并使用

(1)常用API 说明及举例
  1. Method[] methods = clazz.getMethods();获取自己以及父类的所有public修饰的方法对象,
  2. Method[] methods = clazz.getDeclaredMethods();获取自己所有的成员方法,包括private修饰的方法对象;
  3. Method method1 = clazz.getMethod(String name,Class<?>... paramsType); 通过方法名和方法参数列表的Class类型列表获取方法对象;
  4. public Object invoke(Object obj,Object...args)第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数,返回值就是该方法的实际返回值(比如Person类中某个方法有返回值,这里的Object就是接受这个返回值的);
(2)通过获取的方法对象调用该对象的方法
Class<?> clazz = Class.forName(name);
Constructor<?> con = clazz.getDeclaredConstructor(String.class, int.class, String.class);
Object newInstance = con.newInstance("张三",18,"北京");
Log.i(TAG, "onCreate: " + newInstance);

Method method1 = clazz.getMethod("setName",String.class);
method1.setAccessible(true);
method1.invoke(newInstance, "李四");
Log.i(TAG, "onCreate: " + newInstance);

输出结果

Person{address='北京', name='张三', age=18}
Person{address='北京', name='李四', age=18}

可以看到我们通过反射创建对象并初始化时,name=张三,然后通过反射调用setName方法并重新赋值为李四,赋值成功,说明通过method.invoke(Object obj,Object args)可以调用obj的指定方法;

注意:clazz.getMethod("setName",String.class);的参数列表和method1.invoke(newInstance, "李四");的参数列表必须相同,而且一一对应,否则也会报错,理由和构造方法相同,尤其是在有方法重载时要特别注意这个问题。

三、反射的练习

  1. 给一个ArrayList<Integer>的一个集合,需要如何做才能往这个集合中添加字符串数据?

    • 分析:通过list.add()方法肯定不可以,因为编译无法通过。但是我们可以根据泛型的特点来解决,泛型只是在编译时起数据类型检查作用,生成class文件后泛型就不存在,因此如果我们可以在class文件之后向集合中添加数据,就可以绕过泛型检查,像集合中添加数据。而反射实在class文件运行时才执行的,因此满足条件,因此可以通过反射实现这个需求。

    • 代码实现

         ArrayList<Integer> list = new ArrayList<>();
         //只能在运行的时候通过反射获取add方法,然后向里面添加元素
         try {
             Class<?> klass = list.getClass();
             Method add = klass.getDeclaredMethod("add", Object.class);
             add.setAccessible(true);
             add.invoke(list,"sdlfjld");
             add.invoke(list,"dfd");
             add.invoke(list,"kuk");
             add.invoke(list,"cbc");
             for (Object integer : list) {
                 Log.i(TAG, "onCreate: " + integer);
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
      
    • 运行结果

        onCreate: sdlfjld
        onCreate: dfd
        onCreate: kuk
        onCreate: cbc
      
  2. 由于反射可以不用知道具体的对象,就可以创建对象,因此反射经常和泛型配合写框架;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容