Go语言提供了一种机制,在编译时不知道类型的情况下,在运行时,可更新变量、查看值、调用方法以及直接对它的结构成员进行操作,这种机制称为反射(reflection)。
1、Go的反射程序包:reflect
反射功能由reflect包提供,包内定义了两个重要的类型:Type和Value。
Type表示Go语 言的一个类型,它是一个有很多方法的接口,这些方法可以用来识别类型以及透视类型的组 成部分,比如一个结构的各个字段或者一个函数的各个参数。reflect.Type接口只有一个实现,即类型描述符,接口值中的动态类型也是类型描述符。
reflect.Value可以包含一任意类型的值。
通过示例展示Type和Value的使用
package main
import (
"fmt"
"reflect" //引入反射包
)
// 定义用户结构
type User struct {
Id int
Name string
Age int
}
// 定义方法
func (u User)Hello(){
fmt.Println("Hello World!")
}
func main() {
u := User{1,"Tom",12}
// Info(&u) 传入指针类型,编译报错:panic: reflect: NumField of non-struct type
Info(u) // 通过值拷贝输入对象
}
// 反射示例函数
func Info(o interface{}) {
t := reflect.TypeOf(o) // 获取类型数据
fmt.Println("Type:", t.Name()) // 打印类型名称
v := reflect.ValueOf(o) // 获取对象的数值
fmt.Println("Fields:")
for i :=0; i < t.NumField(); i++ {
f := t.Field(i) // 获取指定索引结构成员的信息
val := v.Field(i).Interface() // 获取指定结构成员的值
fmt.Printf("%6s: %v = %v\n", f.Name, f.Type, val) // 打印成员的名称,类型和值
}
for i :=0; i <t.NumMethod(); i++ {
m := t.Method(i) // 获取指定索引的方法的信息
fmt.Printf("%6s: %v\n", m.Name, m.Type) // 打印方法的名称,方法声明
}
}
程序运行输出的结果如下图:

2、匿名字段的反射
在Go语言中,可以通过索引来获取匿名字段的信息,参见程序代码:
package main
import (
"fmt"
"reflect" //引入反射包
)
// 定义用户结构
type User struct {
Id int
Name string
Age int
}
type Manager struct {
User // 定义匿名字段
title string
}
func main() {
m := Manager{User: User{1, "Lisa", 28}, title: "CFO"} // 对象定义及匿名User类型字段初始化
t := reflect.TypeOf(m) // 获取对象的类型信息
fmt.Printf("%#v\n", t.Field(0)) // 通过索引获取匿名字段的信息
fmt.Printf("%#v\n", t.Field(1)) // 获取title字段的信息
// 获取匿名User类型字段成员的方法,通过传入slice来制定索引对应的成员,可以获取相应的信息
fmt.Printf("%#v\n", t.FieldByIndex([]int{0,0})) //获取User中的Id成员
fmt.Printf("%#v\n", t.FieldByIndex([]int{0,1})) //获取User中的Name成员
}
运行输出结果:

上图中,可以看到,对于User类型的匿名成员的名称为:User,是否匿名Anonymous标记为:true,而对于User成员Id和Name来讲,匿名标记为false。
3、通过反射修改结构对象的值
package main
import (
"fmt"
"reflect" //引入反射包
)
// 定义用户结构
type User struct {
Id int
Name string
Age int
}
func main() {
u := User{1,"Tom", 28}
fmt.Println("before:",u)
Set(&u)
fmt.Println("after:",u)
}
func Set(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
fmt.Print("输入的对象是不可修改的")
} else {
v = v.Elem()
}
// 判断是否获取到制定名称的字段信息
f := v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("没有找到Name的字段")
return
}
// 修改字段的数值
if f.Kind() == reflect.String {
f.SetString("Tom2")
}
}
运行结果如下:

reflect 包中还有很多有用的方法,具体详见包的参考文档及源码
网友评论