七叶笔记 » golang编程 » Golang反射原理详解

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(v interface{}, opts encOpts) (err error) {
defer func() {
if r := recover(); r != nil {
if je, ok := r.(jsonError); ok {
err = je.error
} else {
panic(r)
}
}
}()
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}
  

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

 func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
valueEncoder(v)(e, v, opts)
}

func valueEncoder(v reflect.Value) encoderFunc {
if !v.IsValid() {
return invalidValueEncoder
}
return typeEncoder(v.Type())
}

func newTypeEncoder(t reflect.Type, allowAddr bool) 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.
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}

switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
  

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

结尾

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

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

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

相关文章