大家好,我是煎鱼。今天是 2020 年的最后一天,让我们一起继续愉快的学习吧 :)。

在所有的语言中,反射这一功能基本属于必不可少的模块。

虽说 “反射” 这个词让人根深蒂固,但更多的还是 WHY。反射到底是什么,反射又是基于什么法则实现的?

今天我们通过这篇文章来一一揭晓,以 Go 语言为例,了解反射到底为何物,其底层又是如何实现的。

反射是什么

在计算机学中,反射是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。

用比喻来说,反射就是程序在运行的时候能够 “观察” 并且修改自己的行为(来自维基百科)。

简单来讲就是,应用程序能够在运行时观察到变量的值,并且能够修改他。

一个例子

最常见的 reflect 标准库例子,如下:

import (
 "fmt"
 "reflect"
)

func main() {
 rv := []interface{}{"hi", 42, func() {}}
 for _, v := range rv {
  switch v := reflect.ValueOf(v); v.Kind() {
  case reflect.String:
   fmt.Println(v.String())
  case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   fmt.Println(v.Int())
  default:
   fmt.Printf("unhandled kind %s", v.Kind())
  }
 }
}

输出结果:

hi
42
unhandled kind func
interface{}
interface{}interface{}
interface{}
interface{}(type)

这就是我们在编写程序时最常遇见的一个反射使用场景。

Go reflect

reflect.Typereflect.Value
TypeOfValueOf

reflect.TypeOf

演示程序:

func main() {
 blog := Blog{"煎鱼"}
 typeof := reflect.TypeOf(blog)
 fmt.Println(typeof.String())
}

输出结果:

main.Blog
reflect.TypeOfblogmain.Blog

通过人识别的角度来看似乎很正常,但程序就不是这样了。他是怎么知道 “他” 是哪个 package 下的什么呢?

我们一起追一下源码看看:

func TypeOf(i interface{}) Type {
 eface := *(*emptyInterface)(unsafe.Pointer(&i))
 return toType(eface.typ)
}
TypeOf
unsafe.PointeremptyInterfaceinterfacetoTypeType
emptyInterfacertype
type rtype struct {
 size       uintptr
 ptrdata    uintptr 
 hash       uint32 
 tflag      tflag 
 align      uint8  
 fieldAlign uint8  
 kind       uint8   
 equal     func(unsafe.Pointer, unsafe.Pointer) bool
 gcdata    *byte  
 str       nameOff 
 ptrToThis typeOff 
}
rtypeTypeType
Type
type Type interface {
 // 适用于所有类型
 // 返回该类型内存对齐后所占用的字节数
 Align() int

 // 仅作用于 strcut 类型
 // 返回该类型内存对齐后所占用的字节数
 FieldAlign() int

 // 返回该类型的方法集中的第 i 个方法
 Method(int) Method

 // 根据方法名获取对应方法集中的方法
 MethodByName(string) (Method, bool)

 // 返回该类型的方法集中导出的方法的数量。
 NumMethod() int

 // 返回该类型的名称
 Name() string
 ...
}

建议大致过一遍,了解清楚有哪些方法,再针对向看就好。

主体思想是给自己大脑建立一个索引,便于后续快速到 pkg.go.dev 上查询即可。

reflect.ValueOf

演示程序:

func main() {
 var x float64 = 3.4
 fmt.Println("value:", reflect.ValueOf(x))
}

输出结果:

value: 3.4
reflect.ValueOfxreflect.TypeOf
reflect.ValueOf
func ValueOf(i interface{}) Value {
 if i == nil {
  return Value{}
 }

 escapes(i)

 return unpackEface(i)
}

func unpackEface(i interface{}) Value {
 e := (*emptyInterface)(unsafe.Pointer(&i))
 t := e.typ
 if t == nil {
  return Value{}
 }
 f := flag(t.Kind())
 if ifaceIndir(t) {
  f |= flagIndir
 }
 return Value{t, e.word, f}
}
ValueOf
escapesiiemptyInterfacereflect.Value

何时类型转换

reflect
float64inetrface

查看汇编如下:

$ go tool compile -S main.go                         
 ...
 0x0058 00088 ($GOROOT/src/reflect/value.go:2817) LEAQ type.float64(SB), CX
 0x005f 00095 ($GOROOT/src/reflect/value.go:2817) MOVQ CX, reflect.dummy+8(SB)
 0x0066 00102 ($GOROOT/src/reflect/value.go:2817) PCDATA $0, $-2
 0x0066 00102 ($GOROOT/src/reflect/value.go:2817) CMPL runtime.writeBarrier(SB), $0
 0x006d 00109 ($GOROOT/src/reflect/value.go:2817) JNE 357
 0x0073 00115 ($GOROOT/src/reflect/value.go:2817) MOVQ AX, reflect.dummy+16(SB)
 0x007a 00122 ($GOROOT/src/reflect/value.go:2348) PCDATA $0, $-1
 0x007a 00122 ($GOROOT/src/reflect/value.go:2348) MOVQ CX, reflect.i+64(SP)
 0x007f 00127 ($GOROOT/src/reflect/value.go:2348) MOVQ AX, reflect.i+72(SP)
 ...
reflectinterface

reflect.Set

演示程序:

func main() {
 i := 2.33
 v := reflect.ValueOf(&i)
 v.Elem().SetFloat(6.66)
 log.Println("value: ", i)
}

输出结果:

value:  6.66

reflect.ValueOfSetFloat

核心的方法之一就是 Setter 相关的方法,我们可以一起看看其源码是怎么实现的:

func (v Value) Set(x Value) {
 v.mustBeAssignable()
 x.mustBeExported() // do not let unexported x leak
 var target unsafe.Pointer
 if v.kind() == Interface {
  target = v.ptr
 }
 x = x.assignTo("reflect.Set", v.typ, target)
 if x.flag&flagIndir != 0 {
  typedmemmove(v.typ, v.ptr, x.ptr)
 } else {
  *(*unsafe.Pointer)(v.ptr) = x.ptr
 }
}
assignToassignTo

简单来讲就是,检查是否可以设置,接着创建一个新的对象,最后对其修改。是一个非常标准的赋值流程。

反射三大定律

Go 语言中的反射,其归根究底都是在实现三大定律:

  1. Reflection goes from interface value to reflection object.

  2. Reflection goes from reflection object to interface value.

  3. To modify a reflection object, the value must be settable.

我们将针对这核心的三大定律进行介绍和说明,以此来理解 Go 反射里的各种方法是基于什么理念实现的。

第一定律

反射的第一定律是:“反射可以从接口值(interface)得到反射对象”。

示例代码:

func main() {
 var x float64 = 3.4
 fmt.Println("type:", reflect.TypeOf(x))
}

输出结果:

type: float64
xfloat64
float64reflect.TypeOfinterface{}

第二定律

反射的第二定律是:“可以从反射对象得到接口值(interface)”。其与第一条定律是相反的定律,可以是互相补充了。

示例代码:

func main() {
 vo := reflect.ValueOf(3.4)
 vf := vo.Interface().(float64)
 log.Println("value:", vf)
}

输出结果:

value: 3.4
voInterface

第三定律

反射的第三定律是:“要修改反射对象,该值必须可以修改”。第三条定律看上去与第一、第二条均无直接关联,但却是必不可少的,因为反射在工程实践中,目的一就是可以获取到值和类型,其二就是要能够修改他的值。

否则反射出来只能看,不能动,就会造成这个反射很鸡肋。例如:应用程序中的配置热更新,必然会涉及配置项相关的变量变动,大多会使用到反射来变动初始值。

示例代码:

func main() {
 i := 2.33
 v := reflect.ValueOf(&i)
 v.Elem().SetFloat(6.66)
 log.Println("value: ", i)
}

输出结果:

value:  6.66
i2.336.66

但是单看代码,似乎有些 “问题”,怎么设置一个反射值这么 ”麻烦“:

ivElem

本叛逆的 Gophper 表示我就不这么设置,行不行呢,会不会出现什么问题:

func main() {
 i := 2.33
 reflect.ValueOf(i).SetFloat(6.66)
 log.Println("value: ", i)
}

报错信息:

panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x8e)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:259 +0x138
reflect.flag.mustBeAssignable(...)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:246
reflect.Value.SetFloat(0x10b2980, 0xc00001a0b0, 0x8e, 0x401aa3d70a3d70a4)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:1609 +0x37
main.main()
        /Users/eddycjy/go-application/awesomeProject/main.go:10 +0xc5
reflect

这么做的原因在于,Go 语言的函数调用的传递都是值拷贝的,因此若不传指针引用,单纯值传递,那么肯定是无法变动反射对象的源值的。因此 Go 标准库就对其进行了逻辑判断,避免出现问题。

reflectElemSet

总结

通过本文我们学习并了解了 Go 反射是如何使用,又是基于什么定律设计的。另外我们稍加关注,不难发现 Go 的反射都是基于接口(interface)来实现的,更进一步来讲,Go 语言中运行时的功能很多都是基于接口来实现的。

整体来讲,Go 反射是围绕着三者进行的,分别是 Type、Value 以及 Interface,三者相辅相成,而反射本质上与 Interface 存在直接关系,Interface 这一块的内容我们也将在后续的文章进行进一步的剖析。

欢迎持续关注。


分享 Go 语言、微服务架构和奇怪的系统设计

???? 长按关注煎鱼,在知识的海洋里遨游

学习资料分享,关注公众号回复指令:

  • 回复【000】,下载 LeetCode 题解大全。

  • 回复【001】,下载 Go 进阶图书 Mastering Go。