golang的反射机制与实践(上)

写在前面

反射机制是一个很重要的内容,当我们写框架的时候,要想要松耦合,高复用,那么就有很多地方都需要用到反射,可谓是中高级程序员必须掌握的知识点

很多后台语言都有反射机制,但它们的使用原理大多都是一样的

各语言不同的地方,大致就是代码实现方式不一致罢了

其根本,都是从变量得到反射对象,再由反射对象去操作原变量

好了,步入正题

什么是反射

我就用一句话来概括吧

使用反射,可以让我们在程序运行时对任意类型的对象进行操作

注意操作这两个字,操作是指:可以获取对象的信息、改变对象的值、调用对象的方法、甚至是创建一个对象

说到这你可能有点困惑,我们在编写代码的时候不就已经把该实例化的象进行了实例化,该调用的方法都调用了嘛?为什么写程序的时候不调用方法,偏要在运行时去进行这些操作?

其实问题就在这里,如果我们在写程序的时候,一切的对象与方法都能够确定了,那还要反射做什么?

正是因为我们在写程序的时候,要想写一些“万能程序”,用于降低代码的耦合度,所以我们才需要反射,用于处理一些未知的对象

想想,当我们写一个方法,不管别人往我们这个方法内传入什么样的参数,最后我们的函数都能给别人所需要的内容。是不是感觉很牛逼?

反射的使用原理

我这里主要说使用反射的原理,并不是刨析反射的底层原理,有兴趣想要探索原理的读者大人,可以去看看go的reflect包源码

先给你们上个图,看懂这个关系图,后面的文字基本也就可以不看了

image

没看懂没关系,稍微解释就能明白~~

我们定义的一个变量,不管是基本类型int,还是一个结构体Employee,我们都可以通过reflect.TypeOf()获取他的反射类型Type,也可以通过reflect.ValueOf()去获取他的反射值Value

我们学习反射,其实就是学习如何使用原变量,去取得reflect.Type或者reflect.Value这种反射对象;再使用这个反射对象Type以及Value,反过来对原变量进行操作

弄明白了这个道理,那一切都将变得简单

剩下的,我们只是需要去学习reflect包中提供的方法。当我们需要要怎么操作变量,就使用其提供的对应方法即可

反射的注意事项与细节

TypeKind的区别是什么?

Type是类型,Kind是类别,听起来有点绕,他们之间的关系为TypeKind的子集

如果变量是基本类型,那么TypeKind得到的结果是一致的,比如变量为int类型,TypeKind的值相等,都为int

但当变量为结构体时,TypeKind的值就不一样了

我们来看个实际案例

func main() {
    var emp Employee
    emp = Employee{
        Name: "naonao",
        Age:  99,
    }
    rVal := reflect.ValueOf(emp)
    log.Printf("Kind is %v ,Type is %v",
        rVal.Kind(),
        rVal.Type())
    // Kind is struct ,Type is main.Employee
}

可以看到,Kind的值是struct,而Type的值是包名.Employee

反射如何在变量与reflect.Value之间切换?

变量可以转换成interface{}之后,再转换成reflect.Value类型,既然空接口可以转换成Value类型,那么自然也可以反过来转换成变量

用个表达式来表示,就如下所示

变量<----->interface{}<----->reflect.Value

利用空接口来进行中转,这样变量Value之间就可以实现互相转换了

下面我们再说如何用代码实现转换

如何使用反射获取变量本身的值?

这里我们要注意一下,reflect.ValueOf()得到的值是reflect.Value类型,并不是变量本身的值

var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)

这段代码会报错invalid operation: num + rVal (mismatched types int and reflect.Value)

很明显,rVal是属于reflect.Value类型,不能与int类型相加

那怎样才能获得它本身的值呢?

如果是基本类型,比如var num int,那么使用reflect包里提供的转换方法即可reflect.ValueOf(num).Int()

或者是float,那就调用reflect.ValueOf(num).float(),如果是其它的基本类型,需要的时候去文档里面找找即可

如果是我们自己定义的结构体,因为reflect包无法确定我们自己定义了什么结构体,所以本身并不会带有结构体转换的方法,那么我们只能通过类型断言来进行转换

也就是上面说的,利用空接口进行中转,再利用断言进行类型转换,可以看如下代码示例

// Employee 员工
type Employee struct {
    Name string
    Age  int
}

func main() {
    emp := &Employee{
        Name: "naonao",
        Age:  99,
    }
    reflectPrint(emp)
}

func reflectPrint(v interface{}) {
    rVal := reflect.ValueOf(v)   // 获取reflect.Value
    iV := rVal.Interface()       // 利用空接口进行中转
    empVal, ok := iV.(*Employee) // 利用断言转换
    if ok {
        // 如果成功转换则打印结构体
        log.Print(empVal)
    }
}

这里我只是进行了一个简单的判断,如果想要进行完整的判断,还是需要借助swith语句,下篇会提到。也可以参照reflect包的单元测试文件

通过反射来修改变量

先来看看代码如何实现

func main() {
    var num = 1
    modifyValue(&num)// 传递地址
    log.Printf("num is %v", num)// num is 20
}

func modifyValue(i interface{}) {
    rVal := reflect.ValueOf(i)
    rVal.Elem().SetInt(20)
}

细心的你肯定发现了一点异常,函数接收的参数不再是值了,而是接受了一个指针地址

改变值的时候,先调用了Elem()方法,再进行了一个SetInt()的操作

为什么直接传值不行呢?因为reflect包中提供的所有修改变量值的方法,都是对指针进行的操作

那为什么还要先使用Elem()呢?因为Elem()的作用,就是取得指针地址所对应的值,取到值了,我们才能对值进行修改

总不可能连值都没拿到手,就想着去改值吧?

如何理解reflect.Value.Elem()

关于Elem()的使用可以简单的理解为

num := 1
prt *int := &num // 获取num的指针地址
num2 := *ptr // 从指针处取值

因为我们传递了一个地址,所以我们要先拿到这个地址的指针,再通过指针去取得所对应的值

reflect包底层实现就是基于这个原理,不过它的底层代码加了较多的判断,用来保证稳定性

写在最后

这篇先说些基础概念,下篇我们再从实践出发,看看在什么地方需要使用反射,又该如何使用reflect包提供的方法去实现

image

微信扫码关注公众号「闹闹吃鱼」,还可领取Go语言学习大礼包,入门到进阶不再无头绪

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

推荐阅读更多精彩内容

  • [TOC] Golang的反射reflect深入理解和示例 【记录于2018年2月】 编程语言中反射的概念 在计算...
    AllenWu阅读 855评论 1 14
  • 编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机...
    豆瓣奶茶阅读 12,304评论 0 31
  • Golang的反射reflect深入理解和示例 编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够...
    陈卧虫阅读 406评论 0 0
  • 参考《快学 Go 语言》第 15 课 —— 反射golang reflect反射(一):interface接口的入...
    合肥黑阅读 1,335评论 0 18
  • 过了年假,从看似是〃休息日〃而实则是〃忙碌日〃的战争时光转入了恰巧相反的局面。我们人人都步入正常的轨道上来,一切都...
    秦聪1阅读 290评论 0 0