Go 语言
标准库、反射和 cgo
reflect 的用法

reflect 的用法

Go 标准库中提供的 reflect 包让 Go 程序具备运行时的反射能力。反射是程序在运行时访问、检测和修改它本身状态或行为的一种能力。

Go 语言的 interface{} 类型变量具有析出任意类型变量的类型信息(type)和值信息(value)的能力,Go 的反射本质上就是利用 interface{} 的这种能力在运行时对任意变量的类型和值信息进行检视甚至是对值进行修改的机制。

反射三大法则

反射让静态类型语言 Go 在运行时具备了某种基于类型信息的动态特性。 利用这个特性可以在无法提前获知传入参数的真正类型的情况下,依旧可以对其进行正确的格式化输出。 我们来看一个例子:

func reflectType(x interface{}) {
    v := reflect.TypeOf(x)
    fmt.Printf("type:%v\n", v)
}

反射是否适合处理这一类问题,它们的典型特点包括:

  • 输入参数的类型无法提前确定
  • 函数或方法的处理结果因传入参数的不同而异

反射在带来强大的功能的同时,也是很多问题的根源,如:

  • 反射让代码逻辑看起来不那么清晰、难于理解
  • 反射让代码运行得更慢
  • 在编译阶段,编译器无法检测到使用反射的代码中的问题

如果经过评估,还是必须使用反射才能实现想要的功能,那么在使用反射时需要牢记这三大法则:

  • 反射的入口:经由接口(interface)类型变量值传入,通过 reflect.TypeOfreflect.ValueOf 函数获得反射类型对象
  • 反射的出口:反射对象(reflect.Value)应该化身为一个接口(interface)类型变量值的形式走出反射世界
  • 修改反射对象的前提:反射对象对于的 reflect.Value 必须是“Settable”(可配置的)

反射的入口

我们可以通过 reflect.TypeOfreflect.ValueOf 函数对值信息和类型信息进行检视。我们来看一下例子:

var b = true
val := reflect.ValueOf(b)
typ := reflect.TypeOf(b)
println(typ.Name(), val.Bool()) // bool true
 
var i = 23
val = reflect.ValueOf(i)
typ = reflect.TypeOf(i)
println(typ.Name(), val.Int()) // int 23
 
var f = 3.14
val = reflect.ValueOf(f)
typ = reflect.TypeOf(f)
println(typ.Name(), val.Float()) // float64 3.14
 
var s = "hello"
val = reflect.ValueOf(s)
typ = reflect.TypeOf(s)
println(typ.Name(), val.String()) // string hello
 
var fn = func(a, b int) int { return a + b }
val = reflect.ValueOf(fn)
typ = reflect.TypeOf(fn)
println(typ.Kind(), typ.String()) // func func(int, int) int

我们在看一下对原生类型以及自定义类型的检视例子:

var s1 = []int{1, 2, 3}
val = reflect.ValueOf(s1)
typ = reflect.TypeOf(s1)
println(typ.Kind(), typ.String()) // slice []int
 
var arr = [3]int{1, 2, 3}
val = reflect.ValueOf(arr)
typ = reflect.TypeOf(arr)
println(typ.Kind(), typ.String()) // array [3]int
 
var m = map[string]int{"one": 1, "two": 2}
val = reflect.ValueOf(m)
typ = reflect.TypeOf(m)
println(typ.Kind(), typ.String()) // map map[string]int
iter := val.MapRange()
for iter.Next() {
    println(iter.Key(), iter.Value())
}
 
type Person struct {
    Name string
    Age  int
}
p := Person{"Tom", 18}
val = reflect.ValueOf(p)
typ = reflect.TypeOf(p)
println(typ.Kind(), typ.String()) // struct main.Person
println(val.Field(0).String(), val.Field(1).Int()) // Tom 18
 
var ch = make(chan int, 1)
val = reflect.ValueOf(ch)
typ = reflect.TypeOf(ch)
ch <- 1
v, ok := val.TryRecv()
println(v.Int()) // 1
println(typ.Kind(), typ.String(), v, ok) // chan chan int
 
type Myint = int
var i2 Myint = 1
val = reflect.ValueOf(i2)
typ = reflect.TypeOf(i2)
println(typ.Name(), typ.String()) // Myint int

通过反射对象,还可以调用函数或对象的方法:

func Add(a, b int) int {
    return a + b
}
 
type Calculator struct {}
 
func (c *Calculator) Add(a, b int) int {
    return a + b
}
 
func main() {
    f := reflect.ValueOf(Add)
    var i = 5
    var j = 6
    vals := []reflect.Value{reflect.ValueOf(i), reflect.ValueOf(j)}
    ret := f.Call(vals)
    println(ret[0].Int()) // 11
 
    c := reflect.ValueOf(Calculator{})
    m := c.MethodByName("Add")
    ret = m.Call(vals)
    println(ret[0].Int()) // 11
}

我们看到通过函数类型或包含有方法的类型实例反射的 Value 对象,可以通过其 Call 方法调用该函数或类型的方法。 函数或方法的参数以 reflect.Value 类型的切片传入,返回值也是 reflect.Value 类型的切片。

反射的出口

reflect.Value.interface()reflect.ValueOf() 的逆过程,通过 interface 方法我们可以将 reflect.Value 对象还原为 interface{} 类型的变量值。

我们来看一个例子:

func main() {
    var i = 5
    val := reflect.ValueOf(i)
    r := val.Interface().(int)
    println(r) // 5
    r = 6
    println(i, r) // 5 6
 
    val = reflect.ValueOf(&i)
    q := val.Interface().(*int)
    println(*q) // 5
    *q = 6
    println(i // 6
}

我们看到通过 reflect.Value.Interface() 函数重建后得到的新变量与原变量是两个不同的变量,它们的唯一联系就是值相同。 如果我们反射的对象是一个指针,那么通过 reflect.Value.Interface() 重建后得到的新变量是一个指向原变量的指针。 通过新指针变量对所指内存值的修改会反映到原变量上。