02、反射是什么?——《Android打怪升级之旅》

感谢大家和我一起,在Android世界打怪升级!

反射在平时开发中使用几率较小,但在各大框架中会频繁使用(比如:老版本ButterKnife使用注解与反射初始化控件等,省略findViewById),如果有意向成为架构师,这块知识的掌握必不可少。

一、反射是什么

平时开发中创建对象都是通过 new 关键字创建,通过该对象的实例,可以获取该对象的可访问成员变量或者调用可调用方法,此时我们明确知道使用的类是什么。

那如果我们不知道要初始化的类是什么,就需要使用到JAVA为我们提供的反射API了。

1.1 定义

反射可以在程序的运行时

  • 构造任意一个类的对象
  • 了解任意一个对象所属的类
  • 了解任意一个类的成员变量和方法
  • 调用任意一个对象的属性和方法

这种动态获取程序信息以及动态调用对象的功能称为反射机制。反射是JAVA被视为动态语言的关键。

1.2 原理

在运行时获取到类,但是在运行时.java文件已经在编译阶段被编译成了.class文件,所以反射的原理就是:运行时通过字节码文件获取到类的所有信息

1.3 优缺点

优点:

  • 高自由度:可以无视访问权限限制,被private修饰依然可以调用。

缺点:

  • 性能差:反射特别耗时,慢于直接创建对象,所以在使用时要衡量带来的收益是否大于性能的影响。
  • 安全性差:反射的高自由度直接导致类的封装性被破坏。
    • 通过反射修改代码时,由于是直接操作字节码文件,如果对代码不熟悉,及其容易因为修改而导致报错。
    • 反射中有时会直接使用方法名,那在后期维护期间,如果方法名修改被修改,也会产生报错。

二、反射的使用

Class类中方法特别多,我们只以举例的方式写几个常用的例子,大家只需记住通过反射可以获取一个类中所有的成员变量和方法(无视权限),你想要的全都有

2.1 运行时获取类

从1.2章节反射的原理可以晓得,反射的使用需要先在运行时获取到类,运行时获取到类一共有四种方法,根据情况选择:

  • 运行时直接从类中获取

    Class<Fruit> fruitClass1 = Fruit.class;
    
  • 运行时从对象中获取对应的类

    Fruit fruit = new Fruit();
    Class fruitClass2 = fruit.getClass();
    
  • 运行时通过Class类的静态方法获取

    Class fruitClass3 = Class.forName("com.kproduce.androidstudy.test.Fruit");
    
  • 通过类加载器获取

    Class fruitClass4 = getClassLoader().loadClass("com.kproduce.androidstudy.test.Fruit");
    

最终这四种方式获取的Class都是相同的。

// 以下结果都是true
System.out.println(fruitClass1 == fruitClass2);
System.out.println(fruitClass2 == fruitClass3);
System.out.println(fruitClass3 == fruitClass4);

2.2 运行时创建对象

通过在2.1中获取的Class类来创建对象。

// 在2.1中拿到的Class类
Class<Fruit> fruitClass = Fruit.class;

// 调用Class类中的方法创建对象
Fruit fruit = fruitClass.newInstance();

2.3 获取构造方法

一个类的构造方法因为参数不同可以很多,所以有API可以直接获取所有构造方法 或者 根据参数不同获取某个构造方法

// 带有四个构造方法的类
public class Fruit {

    public String name;
    private int type;
    
    public Fruit() {
    }

    public Fruit(String name) {
        this.name = name;
    }

    public Fruit(int type) {
        this.type = type;
    }

    public Fruit(String name, int type) {
        this.name = name;
        this.type = type;
    }
}
  • 获取所有构造方法:getConstructors()

    Constructor<Fruit>[] constructors = (Constructor<Fruit>[]) fruitClass.getConstructors();
    
  • 根据参数获取单个构造方法:getConstructor(@Nullable Class<?>... parameterTypes)

    // 运行时拿到Class
    Class fruitClass = Class.forName("com.kproduce.androidstudy.test.Fruit");
    
    // 1、构造方法:Fruit()
    fruitClass.getConstructor();
    
    // 2、构造方法:Fruit(String)
    fruitClass.getConstructor(String.class);
    
    // 3、构造方法:Fruit(int)
    fruitClass.getConstructor(int.class);
    
    // 4、构造方法:Fruit(String, int)
    fruitClass.getConstructor(String.class, int.class);
    
  • 使用构造方法创建对象

    // 构造方法:Fruit(String, int)
    Constructor<Fruit> constructor = fruitClass.getConstructor(String.class, int.class);
    
    // 根据构造方法 Fruit(String, int) 创建对象
    Fruit apple = constructor.newInstance("苹果", 1);
    

2.4 获取类的所有方法

和获取构造方法类似,有获取所有方法和单个方法的API,但是有两套供选择,注意注释的方法限制。

  • 获取所有方法:getMethods()、getDeclaredMethods()

    // 获取所有方法,包含从父类继承的,不包括private:
    Method[] methods = fruitClass.getMethods();
    
    // 获取所有方法,不包含从父类继承的,包括private:
    Method[] declaredMethods = fruitClass.getDeclaredMethods();
    
  • 根据参数获取单个方法:getMethod("方法名", @Nullable Class<?>... parameterTypes)、getDeclaredMethod("方法名",@Nullable Class<?>... parameterTypes)

    // 获取单个方法,包含从父类继承的,不包括private,可添加参数(参数重载)
    fruitClass.getMethod("方法名", 参数class...);
    
    // 获取单个方法,不包含从父类继承的,包括private,可添加参数(参数重载)
    fruitClass.getDeclaredMethod("方法名", 参数class...);
    
  • 使用方法

    // 获取方法
    Method method = fruitClass.getDeclaredMethod("方法名", 参数class...);
    
    // 如果方法是私有的需要加下面这句
    method.setAccessible(true);
    
    // 调用方法,参数是被调用的对象,方法的调用需要基于对象
    method.invoke(constructor.newInstance("苹果", 1));
    

2.5 获取类的成员变量

和获取方法基本一致,也有两套,可以给变量赋值和取值,都是基于对象的。

  • 获取所有成员变量:getMethods()、getDeclaredMethods()

    // 获取所有变量,包含从父类继承的,不包括private:
    Field[] fields = fruitClass.getFields();
    
    // 获取所有变量,不包含从父类继承的,包括private:
    Field[] declaredFields = fruitClass.getDeclaredFields();
           
    
  • 根据名称获取单个成员变量:getField(@NonNull String name)、getDeclaredField(@NonNull String var1)

    // 获取单个成员变量,包含从父类继承的,不包括private
    Field nameFiled = fruitClass.getField("name");
    
    // 获取单个成员变量,不包含从父类继承的,包括private
    Field typeFiled = fruitClass.getDeclaredField("type");
    
  • 给成员变量赋值和获取值

    // 获取值和赋值都是基于对象,所以先创建对象
    Fruit apple = constructor.newInstance("苹果", 1);
    
    // 获取name的成员变量
    Field nameFiled = fruitClass.getField("name");
    
    // 如果变量是私有的在操作之前需要加下面这句
    nameFiled.setAccessible(true);
    
    // 【取值】获取name的值,值是“苹果”
    Object filed = nameFiled.get(apple);
    
    // 【赋值】给apple对象,设置name的值为“香蕉”
    nameFiled.set(apple, "香蕉");
    
    

总结

最后咱们再总结一下反射的知识点:

  1. 反射可以在程序的运行时,构造任意一个类的对象、了解任意一个对象所属的类、了解任意一个类的成员变量和方法、调用任意一个对象的属性和方法
  2. 反射的原理是:运行时通过字节码文件获取到类的所有信息
  3. 反射的优点是自由度高,可以无视访问权限限制。缺点是性能差、安全性差(破坏了类的封装性)。
  4. 反射需要先在运行时得到类,有四种方式,得到类之后可以了解其中的方法和成员变量。
  5. 反射中对方法的调用、成员变量的取值和赋值,都是基于对象进行操作。

这样反射的介绍就结束了,希望大家读完这篇文章,会对反射有一个更深入的了解。如果我的文章能给大家带来一点点的福利,那在下就足够开心了。

下次再见!


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

推荐阅读更多精彩内容

  • 感谢大家和我一起,在Android世界打怪升级! 泛型,一个所有人都知道怎么用,在JAVA世界老生常谈的特性。更需...
    老匡话Android阅读 354评论 0 0
  • ## 引言 ### java中创建对象有几种方式? #### 1.使用new关键字 #### 2.使用clone方...
    芋头888阅读 575评论 1 0
  • 反射总结慕课网 反射的视频 什么是反射 反射是能够让java代码访问一个已经加载的类的字段,变量,方法和构造器等信...
    付小影子阅读 841评论 0 2
  • 学习Android的同学注意了!!!学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Android学习交流群...
    kingZXY2009阅读 330评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,518评论 28 53