谈谈Go语言的反射三定律

2019-11-10 10:55:49王冬梅

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())

在上面这段代码中,变量 v 是一个可写的反射对象,代码输出也验证了这一点:

settability of v: true

由于变量 v 代表 x, 因此我们可以使用 v.SetFloat 修改 x 的值:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

上面代码的输出如下:

7.1
7.1

反射不太容易理解,reflect.Typereflect.Value 会混淆正在执行的程序,但是它做的事情正是编程语言做的事情。你只需要记住:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

结构体(struct)

在前面的例子中,变量 v 本身并不是指针,它只是从指针衍生而来。把反射应用到结构体时,常用的方式是 使用反射修改一个结构体的某些字段。只要拥有结构体的地址,我们就可以修改它的字段。

下面通过一个简单的例子对结构体类型变量 t 进行分析。

首先,我们创建了反射类型对象,它包含一个结构体的指针,因为后续会修改。

然后,我们设置 typeOfT 为它的类型,并遍历所有的字段。

注意:我们从 struct 类型提取出每个字段的名字,但是每个字段本身也是常规的 reflect.Value 对象。

type T struct {
 A int
 B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
 f := s.Field(i)
 fmt.Printf("%d: %s %s = %vn", i,
  typeOfT.Field(i).Name, f.Type(), f.Interface())
}

上面这段代码的输出如下:

0: A int = 23
1: B string = skidoo

这里还有一点需要指出:变量 T 的字段都是首字母大写的(暴露到外部),因为struct中只有暴露到外部的字段才是“可写的”。

由于变量 s 包含一个“可写的”反射对象,我们可以修改结构体的字段:

f.Interface())s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

上面代码的输出如下:

t is now {77 Sunset Strip}

如果变量 s 是通过 t ,而不是 &t 创建的,调用 SetInt 和 SetString 将会失败,因为 t 的字段不是“可写的”。

结论

最后再次重复一遍反射三定律:

    1.反射可以将“接口类型变量”转换为“反射类型对象”。

    2.反射可以将“反射类型对象”转换为“接口类型变量”。

    3.如果要修改“反射类型对象”,其值必须是“可写的”(settable)。

一旦你理解了这些定律,使用反射将会是一件非常简单的事情。它是一件强大的工具,使用时务必谨慎使用,更不要滥用。