共计 4711 个字符,预计需要花费 12 分钟才能阅读完成。
反射(Go Reflect)
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的 状态 和相关的 语义。
程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
反射就是在运行时动态的获取一个变量的类型信息和值信息。
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang 实现了反射,反射机制就是在 运行时动态的调用对象的方法和属性,官方自带的 reflect 包 就是反射相关的,只要包含这个包就可以使用。
反射的缺点
不过反射也是把双刃剑,功能强大且代码可读性并不理想;若非必要,不推荐使用反射。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发 panic,那很可能是在代码写完的很长时间之后。
- 使用反射后代码可读性会下降
- 反射代码性能低下
Reflect 包
在 Go 语言的反射机制中,任何接口值都由是 一个具体类型
和具体类型的值
两部分组成。在 Go 语言中反射的相关功能由内置的 reflect
包提供,任意接口值 在反射中都可以理解为由 reflect.Type
和reflect.Value
两部分组成,并且 reflect 包提供了 reflect.TypeOf
和reflect.ValueOf
两个函数来获取任意对象的 Value
和Type
。
-
reflect.TypeOf
函数使用
reflect.TypeOf()
函数返回被检查对象的类型Type
:// 返回被检查对象的类型 func reflectType(x interface{}){v := reflect.TypeOf(x) fmt.Println(v) } func main(){ var a float32 = 3.14 reflectType(a) var b int32 = 100 reflectType(b) var c string = "小马" reflectType(c) } /* float32 int32 string */
-
type name
和type kind
在反射中关于类型的划分还分为两种:类型
Type
和底层类型Kind
。在 Go 中可以使用type
关键字声明多种自定义类型,而kind
就是指底层的类型,但在反射中,需要区分指针、结构体等类型时,就需要用到底层类型kind
。例如,定义两个指针类型和两个结构体类型,通过反射
reflect
查看它们的类型和种类:type myInt int64 // 反射 func reflectType(x interface{}) {t := reflect.TypeOf(x) // 打印变量名和底层类型 fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind()) } // 调用 reflectType 返回类型 func main() { var a *float32 // 指针 var b myInt // 自定义类型 var c rune // 类型别名 reflectType(a) // type: kind:ptr reflectType(b) // type:myInt kind:int64 reflectType(c) // type:int32 kind:int32 type person struct { name string age int } var d = person{ name: "完满主任", age: 18, } type class struct{student int} var e = class{88} reflectType(d) // type:person kind:struct reflectType(e) // type:class kind:struct }
【注】:
rune
是int32
的类型别名,同样的,byte
也是uint8
的类型别名。在
reflect
包中定义的底层类型kind
如下所示:type Kind uint const ( Invalid Kind = iota // 非法类型 Bool // 布尔型 Int // 有符号整型 Int8 // 有符号 8 位整型 Int16 // 有符号 16 位整型 Int32 // 有符号 32 位整型 Int64 // 有符号 64 位整型 Uint // 无符号整型 Uint8 // 无符号 8 位整型 Uint16 // 无符号 16 位整型 Uint32 // 无符号 32 位整型 Uint64 // 无符号 64 位整型 Uintptr // 指针 Float32 // 单精度浮点数 Float64 // 双精度浮点数 Complex64 // 64 位复数类型 Complex128 // 128 位复数类型 Array // 数组 Chan // 通道 Func // 函数 Interface // 接口 Map // 映射 Ptr // 指针 Slice // 切片 String // 字符串 Struct // 结构体 UnsafePointer // 底层指针 )
-
reflect.ValueOf
函数使用
reflect.ValureOf
函数返回被检查对象的Value
,其中包含原始值的类型信息,这两个值可以相互转换:// 通过 reflect.ValueOf 反射获取值 func reflectValue(x interface{}){v := reflect.ValueOf(x) k := v.Kind() // 类型判断 switch k { case reflect.Int64: // 从反射中获取整型的原始值,然后强转 fmt.Printf("type is int64, value is %d\n",int64(v.Int())) case reflect.Float32: // 从反射中获取浮点型的原始值,然后强转 fmt.Printf("type is float32, value is %f\n",float32(v.Float())) case reflect.Float64: // 从反射中获取浮点型的原始值,然后强转 fmt.Printf("type is float64, value is %f\n",float64(v.Float())) case reflect.Bool: // 从反射中获取布尔型的原始值,然后强转 fmt.Printf("type is bool, value is %t\n",bool(v.Bool())) default: fmt.Println("nil") } } func main(){ var a float32 = 3.14 var b int64 = 100 var e bool = false reflectValue(a) reflectValue(b) reflectValue(e) // 将 int 类型的原始值转换为 reflect.Value 类型 c := reflect.ValueOf(10) fmt.Printf("type c :%T\n", c) reflectValue(c) } /* type is float32, value is 3.140000 type is int64, value is 100 type is bool, value is false type c :reflect.Value nil */
通过反射修改变量值
想要通过反射修改变量的值,就要知道参数在函数之间传递是值类型还是引用类型,必须是传递变量的内存地址才能修改变量值。反射包
reflect
提供了一个Elem()
方法返回指针对应的值:func reflectSetValue1 (x interface{}){v := reflect.ValueOf(x) if v.Kind() == reflect.Int64{v.SetInt(250) // 修改副本,会抛出 panic 异常 } } func reflectSetValue2 (x interface{}){v := reflect.ValueOf(x) // 使用 Elem()方法修改变量的值 if v.Elem().Kind() == reflect.Int64{v.Elem().SetInt(251) } } func main(){ var a int64 //reflectSetValue1(a) //panic: reflect: reflect.flag.mustBeAssignable using unaddressable value reflectSetValue2(&a) fmt.Printf("type is int64, value is %d\n",a) } /* type is int64, value is 251 */
【注】:如果调用方法
reflectSetValue1(a)
,则编译器会抛出一个panic
异常结构体反射
任意值通过
reflect.TypeOf()
获得反射对象的类型信息后,如果它的类型是结构体struct
,可以通过反射值对象reflect.Type
中NumField()
和Field()
方法获得结构体成员的详细信息:
### StructField 类型
StructField
类型用来描述结构体中的一个字段的信息:
type StructField struct {
// Name 是字段的名字。PkgPath 是非导出字段的包路径,对导出字段该字段为 ""。// 参见 http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于 Type.FieldByIndex 时的索引切片
Anonymous bool // 是否匿名字段
}
### 结构体反射(demo)
当我们使用反射得到一个结构体之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息:
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main(){
// 初始化
stu1 := student{
Name: "小马",
Score: 99,
}
// 返回类型
t := reflect.TypeOf(stu1)
fmt.Printf("name:%s kind:%v\n",t.Name(),t.Kind())
// 通过 for 循环遍历结构体的所有字段信息
for i:= 0 ; i<t.NumField() ;i++ {field :=t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}
// 通过字段名返回指定结构体的信息
if scoreField,ok := t.FieldByName("Score");ok{fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
/*
name:student kind:struct
name:Name index:[0] type:string json tag:name
name:Score index:[1] type:int json tag:score
name:Score index:[1] type:int json tag:score
*/