Golang反射原理详解

Golang反射原理详解

反射的概念

适用场景

反射的优缺点

反射的优点

反射的缺点

Golang的反射

Golang反射的基本原理

Golang反射提供的能力

Go反射三定律

Golang中反射的实际运用

结尾

Golang反射原理详解

反射是计算机语言提供的一个关键特性,掌握它,对我们编写通用(不要写死)的代码有比较大的帮助,另外,一些库或者框架提供的关键特性也是通用反射来实现,掌握反射,可以使我们更好理解这些功能的实现.

本文试图通过反射的概念,适用场景,Golang中的反射,Golang的实际运用 四个方面来搞懂反射.

全文看完大概需要15分钟左右,如时间不充裕,建议收藏后仔细阅读.

反射的概念

反射本来是一个物理学中的名词,比如光的反射,声音的反射等.

这里说的是反射是指反射编程, 本文中要讨论的反射都是指反射编程. 根据维基百科的定义,反射编程是指在程序运行期间,可以访问,检测和修改它本身状态或者行为的一种能力.用比喻来说,反射就是程序运行的时候能够"观察"并且修改自己的行为的一种能力.

适用场景

不能预先知道函数参数类型,或者参数类型有很多种,无法用同一个类型来表示

函数需要根据入参来动态的执行不同的行为

反射的优缺点

反射的优点

可以在一定程序上避免硬编码,提供灵活性和通用性

可以作为一个第一个类对象发现并修改源代码的结构(如代码块,类,方法,协议等)

反射的缺点

由于将部分类型检查工作从编译期推迟到了运行时,使得一些隐藏的问题无法通过编译期发现,提高了代码出现bug的几率,搞不好就会panic

反射出变量的类型需要额外的开销,降低了代码的运行效率

反射的概念和语法比较抽象,过多的使用反射,使得代码难以被其他人读懂,不利于合作与交流

Golang的反射

Golang反射的基本原理

Golang是怎么实现在程序运行的时候能够"观察"并且修改自己的行为的能力的呢

Golang反射是通过接口来实现的,通过隐式转换,普通的类型被转换成interface类型,这个过程涉及到类型转换的过程,首先从Golang类型转为interface类型, 再从interface类型转换成反射类型, 再从反射类型得到想的类型和值的信息.

总的基本流程见下面的图.


在Golang obj转成interface这个过程中, 分2种类型

包含方法的interface, 由runtime.iface实现

不包含方法的interface, 由runtime.eface实现

这2个类型都是包含2个指针, 一个是类型指针, 一个是数据指针, 这2个指针是完成反射的基础.

实质上, 通过上述转换后得到的2种interface, 已经可以实现反射的能力了. 但作为语言本身, 标准库将这个工作封装好了, 就是 reflect.Type与reflect.Value , 方便我们使用反射.

reflect. TypeOf 和 reflect.ValueOf 是一个转换器, 完成反射的的最终转换, 得到 reflect.Type, reflect.Value 对象, 得到这2个对象后, 就可以完成反射的准备工作了, 通过 reflect.Type, reflect.Value 这对类型, 可以实现反射的能力.

上图中, 最后一根线说的是由reflect. Value变成普通inteface的过程, 然后通过具体的类型断言, 转成真正的类型. 可能有人会觉得奇怪, 为什么 reflect.Value 可以转成interface对象, reflect. Type 不行呢, 这个留给读这个文章的你去思考, 相信会有答案.

Golang反射提供的能力

运行时获取对象的类型, 值

创建对象, 执行方法

反射对象转换成Go语言对象

动态修改对象的值

已经有人将这些能力总结成反射三定律

Go反射三定律

如同物理反射定律一样, 反射编程中也有反射定律. 这个反射定律是在go语言官方博客中, The Laws of Reflection 有兴趣的可以点开一看. 本文简单概括一下就是下面三点.

Golang对象可以转换成反射对象

反射对象可以转换成Golang对象

可寻址的reflect对象可以更新值

Golang中反射的实际运用

反射在标准库中和第三方库中有着大量的运用, 这里举几个子来说明反射的运用

encoding/json marshal方法

fmt. Printf

各种orm工具

下面仅列举json序列化的例子

func(e*encodeState)marshal(vinterface{},opts encOpts)(errerror){deferfunc(){ifr:=recover();r!=nil{ifje,ok:=r.(jsonError);ok{err=je.error}else{panic(r)}}}()e.reflectValue(reflect.ValueOf(v),opts)returnnil}

这里将interface v通过 reflect.ValueOf 转换成 reflect.Value 对象进一步做下处理.

func(e*encodeState)reflectValue(v reflect.Value,opts encOpts){valueEncoder(v)(e,v,opts)}funcvalueEncoder(v reflect.Value)encoderFunc{if!v.IsValid(){returninvalidValueEncoder}returntypeEncoder(v.Type())}funcnewTypeEncoder(t reflect.Type,allowAddrbool)encoderFunc{// If we have a non-pointer value whose type implements// Marshaler with a value receiver, then we're better off taking// the address of the value - otherwise we end up with an// allocation as we cast the value to an interface.ift.Kind()!=reflect.Ptr&&allowAddr&&reflect.PtrTo(t).Implements(marshalerType){returnnewCondAddrEncoder(addrMarshalerEncoder,newTypeEncoder(t,false))}ift.Implements(marshalerType){returnmarshalerEncoder}ift.Kind()!=reflect.Ptr&&allowAddr&&reflect.PtrTo(t).Implements(textMarshalerType){returnnewCondAddrEncoder(addrTextMarshalerEncoder,newTypeEncoder(t,false))}ift.Implements(textMarshalerType){returntextMarshalerEncoder}switcht.Kind(){casereflect.Bool:returnboolEncodercasereflect.Int,reflect.Int8,reflect.Int16,reflect.Int32,reflect.Int64:returnintEncodercasereflect.Uint,reflect.Uint8,reflect.Uint16,reflect.Uint32,reflect.Uint64,reflect.Uintptr:returnuintEncodercasereflect.Float32:returnfloat32Encodercasereflect.Float64:returnfloat64Encodercasereflect.String:returnstringEncodercasereflect.Interface:returninterfaceEncodercasereflect.Struct:returnnewStructEncoder(t)casereflect.Map:returnnewMapEncoder(t)casereflect.Slice:returnnewSliceEncoder(t)casereflect.Array:returnnewArrayEncoder(t)casereflect.Ptr:returnnewPtrEncoder(t)default:returnunsupportedTypeEncoder}}

这里通过 reflect.Type 类型, 来初始化不同的encoder, 大量运用了反射, 实现了序列化, 在不支持反射的语言如c++, 实现对象json序列化, 就比较麻烦.

结尾

Golang反射严重依赖于 interface{} 这个万能的 容器 类型, 这个 interface{} 类型相当于java中的class类型, 是实现反射的桥梁. 我们在谈Golang反射时, 主要还是围绕 interface{} 展开来说的.

反射是现代静态语言的通用底层技术, 能在一定程序上提升静态类型的灵活性.

全文完, 如果你觉得有用, 欢迎点赞, 收藏, 关注 三连, 谢谢阅读. 如有你对本文有要需要讨论的点, 欢迎留言讨论.

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

推荐阅读更多精彩内容