反射让静态类型语言 Go 在运行时具备了某种基于类型信息的 “动态特性”,利用这种特性,fmt.Println 在无法提前获知传入参数的真正类型的情况下依旧可以对其进行正确地格式化输出;json.Marshal 也是通过这种特性对传入的任意结构体类型进行 “解构” 并正确生成对应的 JSON 文本。下面我们通过一个简单的构建 SQL 查询语句的例子来更为直观地感受 Go 反射的 “魔法”:
// go-reflect/construct_sql_query_stmt.gopackage mainimport ("bytes""errors""fmt""reflect""time")func ConstructQueryStmt(obj interface{}) (stmt string, err error) {// 仅支持struct或struct指针类型typ := reflect.TypeOf(obj)if typ.Kind() == reflect.Ptr {typ = typ.Elem()}if typ.Kind() != reflect.Struct {err = errors.New("only struct is supported")return}buffer := bytes.NewBufferString("")buffer.WriteString("SELECT ")if typ.NumField() == 0 {err = fmt.Errorf("the type[%s] has no fields", typ.Name())return}for i := 0; i < typ.NumField(); i++ {field := typ.Field(i)if i != 0 {buffer.WriteString(", ")}column := field.Nameif tag := field.Tag.Get("orm"); tag != "" {column = tag}buffer.WriteString(column)}stmt = fmt.Sprintf("%s FROM %s", buffer.String(), typ.Name())return}type Product struct {ID uint32Name stringPrice uint32LeftCount uint32 `orm:"left_count"`Batch string `orm:"batch_number"`Updated time.Time}type Person struct {ID stringName stringAge uint32Gender stringAddr string `orm:"address"`Updated time.Time}func main() {stmt, err := ConstructQueryStmt(&Product{})if err != nil {fmt.Println("construct query stmt for Product error:", err)return}fmt.Println(stmt)stmt, err = ConstructQueryStmt(Person{})if err != nil {fmt.Println("construct query stmt for Person error:", err)return}fmt.Println(stmt)}
我们来看一下上述示例的运行结果:
$go run construct_sql_query_stmt.goSELECT ID, Name, Price, left_count, batch_number, Updated FROM ProductSELECT ID, Name, Age, Gender, address, Updated FROM Person
Rob Pike 还为 Go 反射的规范使用定义了三大法则,如果经过评估,你必须使用反射才能实现你要的功能特性,那么你在使用反射时需要牢记这三条法则:
- 反射世界的入口:经由接口 (interface{}) 类型变量值进入到反射的世界并获得对应的反射对象 (reflect.Value 或 reflect.Type);
- 反射世界的出口:反射对象 (reflect.Value) 通过化身为一个接口 (interface{}) 类型变量值的形式走出反射世界;
- 修改反射对象的前提:其对应的 reflect.Value 必须是可设置的 (Settable)。
对于前两条法则,我们可以用下面的图来表示:

- 进入到反射世界的入口:reflect.TypeOf 和 reflect.ValueOf
// reflect.ValueOf().Type() 等价于 reflect.TypeOf()var i int = 5val := reflect.ValueOf(i)typ := reflect.TypeOf(i)fmt.Println(reflect.DeepEqual(typ, val.Type())) // true
反射世界入口可以获取 Go 变量实例的类型信息和值信息的关键在于它们利用了 interface{} 类型形式参数对传入的实际参数 (Go 变量实例) 的析构能力 (可参考第 26 条 “了解接口类型变量的内部表示”),两个入口函数分别将得到的值信息和类型信息存储在 reflect.Value 对象和 reflect.Type 对象中。
果然是在传参的时候元信息就被创建了.
进入反射世界后,我们就可以通过 reflect.Value 实例和 reflect.Type 实例进行值信息和类型信息的检视。我们先来看看对 Go 的一些简单原生类型的检视结果:
// go-reflect/examine_value_and_type.go// 简单原生类型var b = true // 布尔类型val := reflect.ValueOf(b)typ := reflect.TypeOf(b)fmt.Println(typ.Name(), val.Bool()) // bool truevar i = 23 // 整型val = reflect.ValueOf(i)typ = reflect.TypeOf(i)fmt.Println(typ.Name(), val.Int()) // int 23var f = 3.14 // 浮点型val = reflect.ValueOf(f)typ = reflect.TypeOf(f)fmt.Println(typ.Name(), val.Float()) // float64 3.14var s = "hello, reflection" // 字符串val = reflect.ValueOf(s)typ = reflect.TypeOf(s)fmt.Println(typ.Name(), val.String()) //string hello, reflectionvar fn = func(a, b int) int { // 函数(一等公民)return a + b}val = reflect.ValueOf(fn)typ = reflect.TypeOf(fn)fmt.Println(typ.Kind(), typ.String()) // func func(int, int) int
reflect.Value 类型拥有很多方便我们进行值检视的方法,比如:Bool、Int、String 等,但显然这些方法不能对所有的变量类型都适用,比如:Bool 方法仅适用于对布尔类型变量进行反射后得到的 Value 对象。一旦应用的方法与 Value 对象的值类型不匹配,我们将收到运行时 panic:
使用前需要检查类型.
var i = 17val := reflect.ValueOf(i)fmt.Println(val.Bool()) // panic: reflect: call of reflect.Value.Bool on int Value
reflect.Type 是一个接口类型,它包含了很多用于检视类型信息的方法,而对于简单原生类型来说,通过 Name、String 或 Kind 方法就可以得到我们想要的类型名称或类型类别等信息。Name 方法返回有确定定义的类型的名字 (不包括包名前缀),比如:int、string,对于上面的函数类型变量,Name 方法将返回空;我们可以通过 String 方法得到类型的描述字符串,比如上面的 func(int, int) int。String 方法返回的类型描述可能包含包名 (一般使用短包名,即仅使用包导入路径的最后一段),比如:main.Person;Type 接口的 Kind 方法则返回类型的特定类别,比如下面的两个变量 pi 和 ps 虽然是不同类型的指针,但是它们的 Kind 都是 ptr:
var pi = (*int)(nil)var ps = (*string)(nil)typ := reflect.TypeOf(pi)fmt.Println(typ.Kind(), typ.String()) // ptr *inttyp = reflect.TypeOf(ps)fmt.Println(typ.Kind(), typ.String()) // ptr *string
接下来我们再来看看对原生复合类型以及其他自定义类型的检视结果:
接下来我们再来看看对原生复合类型以及其他自定义类型的检视结果:
// go-reflect/examine_value_and_type.go// 原生复合类型var sl = []int{5, 6} // 切片val = reflect.ValueOf(sl)typ = reflect.TypeOf(sl)fmt.Printf("[%d %d]\n", val.Index(0).Int(),val.Index(1).Int()) // [5, 6]fmt.Println(typ.Kind(), typ.String()) // slice []intvar arr = [3]int{5, 6} // 数组val = reflect.ValueOf(arr)typ = reflect.TypeOf(arr)fmt.Printf("[%d %d %d]\n", val.Index(0).Int(),val.Index(1).Int(), val.Index(2).Int()) // [5 6 0]fmt.Println(typ.Kind(), typ.String()) // array [3]intvar m = map[string]int{ // map"tony": 1,"jim": 2,"john": 3,}val = reflect.ValueOf(m)typ = reflect.TypeOf(m)iter := val.MapRange()fmt.Printf("{")for iter.Next() {k := iter.Key()v := iter.Value()fmt.Printf("%s:%d,", k.String(), v.Int())}fmt.Printf("}\n") // {tony:1,jim:2,john:3,}fmt.Println(typ.Kind(), typ.String()) // map map[string]inttype Person struct {Name stringAge int}var p = Person{"tony", 23} // 结构体val = reflect.ValueOf(p)typ = reflect.TypeOf(p)fmt.Printf("{%s, %d}\n", val.Field(0).String(),val.Field(1).Int()) // {"tony", 23}fmt.Println(typ.Kind(), typ.Name(), typ.String()) // struct Person main.Personvar ch = make(chan int, 1) // channelval = reflect.ValueOf(ch)typ = reflect.TypeOf(ch)ch <- 17v, ok := val.TryRecv()if ok {fmt.Println(v.Int()) // 17}fmt.Println(typ.Kind(), typ.String()) // chan chan int// 其他自定义类型type MyInt intvar mi MyInt = 19val = reflect.ValueOf(mi)typ = reflect.TypeOf(mi)fmt.Println(typ.Name(), typ.Kind(), typ.String(), val.Int()) // MyInt int main.MyInt 19
通过反射对象,我们还可以调用函数或对象的方法:
// go-reflect/call_func_and_method.go... ...func Add(i, j int) int {return i + j}type Calculator struct{}func (c Calculator) Add(i, j int) int {return i + j}func main() {// 函数调用f := reflect.ValueOf(Add)var i = 5var j = 6vals := []reflect.Value{reflect.ValueOf(i), reflect.ValueOf(j)}ret := f.Call(vals)fmt.Println(ret[0].Int()) // 11// 方法调用c := reflect.ValueOf(Calculator{})m := c.MethodByName("Add")ret = m.Call(vals)fmt.Println(ret[0].Int()) // 11}
我们看到通过函数类型变量或包含有方法的类型实例反射出的 Value 对象,我们可以通过其 Call 方法调用该函数或类型的方法。函数或方法的参数以 reflect.Value 类型切片的形式提供,函数 / 方法的返回值也以 reflect.Value 类型切片的形式返回。不过务必保证 Value 参数的类型信息与原函数 / 方法的参数的类型相匹配,否则也会导致运行时 panic:
// go-reflect/call_func_and_method.govar k float64 = 3.14ret = m.Call([]reflect.Value{reflect.ValueOf(i),reflect.ValueOf(k)}) // panic: reflect: Call using floa t64 as type int
- 出口:通过 reflect.Value.Interface() 将 reflect.Value 对象恢复成一个 interface{} 类型变量值
reflect.Value.Interface() 是 reflect.ValueOf() 的逆过程,通过 Interface 方法我们可以将 reflect.Value 对象恢复成一个 interface{} 类型变量值。这个离开反射世界的过程实质是将 reflect.Value 中的类型信息和值信息重新打包成一个 interface{} 的内部表示。之后,我们就可以像上图中展示的那样,通过类型断言 (type assertion) 得到一个反射前的类型变量值:
// go-reflect/reflect_value_to_interface.go... ...func main() {var i = 5val := reflect.ValueOf(i)r := val.Interface().(int)fmt.Println(r) // 5r = 6fmt.Println(i, r) // 5 6val = reflect.ValueOf(&i)q := val.Interface().(*int)fmt.Printf("%p, %p, %d\n", &i, q, *q) // 0xc0000b4008, 0xc0000b4008, 5*q = 7fmt.Println(i) // 7}
注意对指针的使用.
- 输出参数、interface{} 类型变量以及反射对象的可设置性 (Settable)
在学习传统编程语言 (比如 C 语言) 的函数概念的时候,通常会有输入参数、输出参数的概念,当然 Go 语言也是支持这些概念的,比如下面例子:
func myFunc(in int, out *int) {in = 1*out = in + 10}func main() {var n = 17var m = 23fmt.Printf("n=%d, m=%d\n", n, m) // n=17, m=23myFunc(n, &m)fmt.Printf("n=%d, m=%d\n", n, m) // n=17, m=11}
对于以 interface{} 类型变量 i 作为形式参数的 reflect.ValueOf 和 reflect.TypeOf 函数来说,i 自身是被反射对象的 “复制品”,就像上面函数的输入参数那样。而新创建的反射对象又拷贝了 i 中所包含的值信息,因此当被反射的对象以值类型 (T) 传递给 reflect.ValueOf 时,在反射世界中对反射对象的值信息的修改不会对被反射对象产生影响,于是 Go 的设计者们认为这种修改毫无意义,并且禁止了这种行为,一旦发生这种行为,将会导致运行时 panic:
var i = 17val := reflect.ValueOf(i)val.SetInt(27) // panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
reflect.Value 提供了 CanSet、CanAddr 以及 CanInterface 等方法可以帮助我们判断反射对象是否是可设置的、可寻址的以及可恢复为一个 interface{} 类型变量。我们来看一个具体的例子:
// go-reflect/reflect_value_settable.go... ...type Person struct {Name stringage int}func main() {var n = 17fmt.Println("int:")val := reflect.ValueOf(n)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false truefmt.Println("\n*int:")val = reflect.ValueOf(&n)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = reflect.ValueOf(&n).Elem()fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true truefmt.Println("\nslice:")var sl = []int{5, 6, 7}val = reflect.ValueOf(sl)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = val.Index(0)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true truefmt.Println("\narray:")var arr = [3]int{5, 6, 7}val = reflect.ValueOf(arr)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = val.Index(0)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false truefmt.Println("\nptr to array:")var pArr = &[3]int{5, 6, 7}val = reflect.ValueOf(pArr)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = val.Elem()fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true trueval = val.Index(0)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true truefmt.Println("\nstruct:")p := Person{"tony", 33}val = reflect.ValueOf(p)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval1 := val.Field(0) // Namefmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val1.CanSet(), val1.CanAddr(), val1.CanInterface()) // false false trueval2 := val.Field(1) // agefmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val2.CanSet(), val2.CanAddr(), val2.CanInterface()) // false false falsefmt.Println("\nptr to struct:")pp := &Person{"tony", 33}val = reflect.ValueOf(pp)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = val.Elem()fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true trueval1 = val.Field(0) // Namefmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val1.CanSet(), val1.CanAddr(), val1.CanInterface()) // true true trueval2 = val.Field(1) // agefmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val2.CanSet(), val2.CanAddr(), val2.CanInterface()) // false true falsefmt.Println("\ninterface:")var i interface{} = &Person{"tony", 33}val = reflect.ValueOf(i)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval = val.Elem()fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // true true truefmt.Println("\nmap:")var m = map[string]int{"tony": 23,"jim": 34,}val = reflect.ValueOf(m)fmt.Printf("Settable = %v, CanAddr = %v, CanInterface = %v\n",val.CanSet(), val.CanAddr(), val.CanInterface()) // false false trueval.SetMapIndex(reflect.ValueOf("tony"), reflect.ValueOf(12))fmt.Println(m) // map[jim:34 tony:12]}
通过上述例子,我们看到当被反射的对象以值类型 (T) 传递给 reflect.ValueOf 时,所得到的反射对象 (Value) 是不可设置的和不可寻址的;当被反射的对象以指针类型 (*T或&T) 传递给 reflect.ValueOf 时,我们通过 reflect.Value 的 Elem 方法可以得到代表着该指针所指内存对象的 Value 反射对象,而这个反射对象则是可设置和可寻址的,
相当于指针解引用到了原对象.
对其进行修改 (比如利用 Value 的 SetInt 方法) 将会像函数的输出参数那样直接修改被反射对象所指向的内存空间的值;同时,当传入结构体或数组指针时,我们通过 Field 或 Index 方法得到的代表结构体字段或数组元素的 Value 反射对象也是可设置和可寻址的;如果结构体中某个字段是非导出字段,则该字段是可寻址但不是可设置的 (比如上面例子中的 age 字段);
当被反射的对象的静态类型是接口类型时 (就像上面的 interface{} 类型变量 i),该被反射对象的动态类型决定了其进入反射世界后的可设置性。如果动态类型为 *T或&T 时,就像上面传给变量 i 的是 &Person{},那么通过 Elem 方法获得的反射对象就是可设置和可寻址的;map 类型被反射对象是一个特殊的存在,它的 key 和 value 都是不可寻址和不可设置的,但我们可以通过 Value 提供的 SetMapIndex 方法对 map 反射对象进行修改,这种修改会同步到被反射的 map 变量中。
