反射是指在程序运行期对程序本身进行访问和修改的能力。 程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。
说白了反射就是让程序能够在运行的时候判断变量的类型等信息。如果是结构体变量,还可以获取到结构体本身的信息
应用场景:
- 序列号和反序列化,json protobuf等协议
Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。
reflect 包
官方文档解释:
reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。
func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。reflect.TypeOf可以获得任意的类型对象,程序通过类型对象可以访问任意值的类型信息。
Type类型官方文档的介绍如下:
type Type interface {
// Kind返回该接口的具体分类
Kind() Kind
// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
Name() string
// PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
// 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
}
func ValueOf(i interface{}) Value // 传入参数是空接口,因为为了能够操作任意值
ValueOf返回一个初始化为接口保管的具体值的Value,ValueOf(nil)返回Value零值。
type Person struct{
Name string `json:"name"`
Age int `json:"age"`
Birthday string `json:"irthday"`
Salary float32 `json:"salary"`
Skill string `json:"skill"`
}
func main(){
person := Person{
Name:"ywh",
Age:23,
Birthday:"2021-1-1",
Salary:12314124.1231,
Skill:"mk",
}
rType := reflect.TypeOf(person) //返回一个reflect.Type类型
fmt.Println(rType.Name(),rType.Kind()) //调用Type类型对应的方法
rValue := reflect.ValueOf(person) //返回一个reflect.Value类型
fmt.Println(rValue.Kind()) //调用Value类型对应的方法
}
Person struct
struct
reflect.Type 中的 Name() 方法,返回表示类型名称的字符串;类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。
直观的感觉Name()返回的类型被包含在Kind()返回的类型中,也就是说Person属于struct
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 创建cat的实例
ins := &cat{}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 显示反射类型对象的名称和种类
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
// 取类型的元素
typeOfCat = typeOfCat.Elem()
// 显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
// name:'' kind:'ptr'
// element name: 'cat', element kind: 'struct'
下面先介绍下反射的几大定律:
-
反射可以将接口类型变量转化为反射类型变量
注:这里反射类型指的是reflect.Type和reflect.Value
type Person struct{ Name string `json:"Pname"` Age int `json:"Page"` Birthday string `json:"Pbirthday"` Salary float32 `json:"Psalary"` Skill string `json:"Pskill"` } func main(){ person := Person{ Name:"ywh", Age:23, Birthday:"2021-1-1", Salary:12314124.1231, Skill:"mk", } rType := reflect.TypeOf(person)//返回一个reflect.Type类型 fmt.Printf("%v %T",rType,rType) }main.Person *reflect.rtype可能会疑惑,没有看到接口啊!其实在 reflect.TypeOf 的函数签名里包含一个空接口
func TypeOf(i interface{}) Type,调用 reflect.TypeOf(person) 时,person 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。类型 reflect.Type 和 reflect.Value 都有很多方法,我们可以检查和使用它们,这里我们举几个例子。
func (v Value) Elem() ValueElem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
也就是说可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个
*操作type Type interface{ Elem() Type // 返回map类型的键的类型。如非映射类型将panic ... }func main(){ Ptrperson := &Person{ Name:"ywh", Age:23, Birthday:"2021-1-1", Salary:12314124.1231, Skill:"mk", } rType := reflect.TypeOf(Ptrperson) fmt.Printf("name:%v kind:%v\n",rType.Name(),rType.Kind()) rType = rType.Elem() fmt.Printf("name:%v kind:%v\n",rType.Name(),rType.Kind()) }name: kind:ptr //可以看到是一个指针类型 name:Person kind:struct使用反射获取结构体成员类型: 重要
可以通过反射值结构体类型对象 reflect.Type 的 NumField() 和 Field() 方法获得结构体成员的详细信息。
NumField() int // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic Field(i int) StructField // 返回索引序列指定的嵌套字段的类型, // 等价于用索引中每个值链式调用本方法,如非结构体将会panic FieldByIndex(index []int) StructField // 返回该类型名为name的字段(会查找匿名字段及其子字段), // 布尔值说明是否找到,如非结构体将panic FieldByName(name string) (StructField, bool) // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic FieldByNameFunc(match func(string) bool) (StructField, bool) // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真 // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)StructField 的结构如下:
type StructField struct { Name string // 字段名 PkgPath string // 字段路径 Type Type // 字段反射类型对象 Tag StructTag // 字段的结构体标签 Offset uintptr // 字段在结构体中的相对偏移 Index []int // Type.FieldByIndex中的返回的索引值 Anonymous bool // 是否为匿名字段 }同样的,Value类型也有对应的方法:
func (v Value) NumField() int返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
func (v Value) Field(i int) Value返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
func (v Value) FieldByIndex(index []int) Value返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic
func (v Value) FieldByName(name string) Value返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。
type Person struct{ Name string `json:"Pname"` Age int `json:"Page"` Birthday string `json:"Pbirthday"` Salary float32 `json:"Psalary"` Skill string `json:"Pskill"` } func main(){ Ptrperson := &Person{ Name:"ywh", Age:23, Birthday:"2021-1-1", Salary:12314124.1231, Skill:"mk", } rType := reflect.TypeOf(Ptrperson).Elem() for i := 0; i < rType.NumField();i++{ //使用 reflect.Type 类型的 NumField() 方法获得一个结构体类型共有多少个字段。 //reflect.Type 中的 Field() 方法和 NumField 一般都是配对使用,用来实现结构体成员的遍历操作。 fieldType := rType.Field(i)//返回的是StructField结构体 fmt.Printf("Name:%v Tag:%v \n",fieldType.Name,fieldType.Tag) } /* 使用 reflect.Type 的 FieldByName() 根据字段名查找结构体字段信息,catType 表示返回的结构体字段信息,类型为 StructField,ok 表示是否找到结构体字段的信息。 */ if catType,ok := rType.FieldByName("Name"); ok{ fmt.Println(catType.Tag.Get("json")) } }Name:Name Tag:json:"Pname" Name:Age Tag:json:"Page" Name:Birthday Tag:json:"Pbirthday" Name:Salary Tag:json:"Psalary" Name:Skill Tag:json:"Pskill" Pname再提一下,上面结构体成员变量后面的字符串是结构体标签
结构体标签是对结构体字段的额外信息标签。
- 结构体标签格式
`key1:"value1" key2:"value2"`- 从结构体标签中获取值
func (tag StructTag) Get(key string) string //StructTag.Get("json") -
反射可以将反射类型变量转化为接口类型变量
根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。
func (v Value) Interface() interface{}事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。之后,我们可以进行类型断言,恢复底层的具体值:
func main(){ var x int = 21 rValue := reflect.ValueOf(x) //返回的是reflect.Value类型对象 val := rValue.Interface().(int) //先返回interface{}类型,然后再类型断言为int fmt.Println(val) }21为什么不直接使用 fmt.Println(v)?因为 v 的类型是 reflect.Value,我们需要的是它的具体值
-
如果要修改反射类型对象其值必须是“可写的”
func main(){ var x int = 21 rValue := reflect.ValueOf(x) //返回的是reflect.Value类型对象 val := rValue.Interface().(int) //先返回interface{}类型,然后再类型断言为int fmt.Println(val) rValue.SetInt(100) //运行时会报错!!! }panic: reflect: reflect.Value.SetInt using unaddressable value这里问题不在于值100不能被寻址,而是因为变量 rValue 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。
判断是否可写可以利用
func (v Value) CanSet() bool方法如何修改呢?想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库。
func main(){ var x int = 21 rValuePtr := reflect.ValueOf(&x) //返回的是reflect.Value类型对象 fmt.Printf("%v %T\n",rValuePtr,rValuePtr) val := rValuePtr.Elem() fmt.Printf("%v %T\n",val,val) val.SetInt(100) fmt.Printf("%v\n",val.Interface()) }0xc00000a0e0 reflect.Value 21 reflect.Value 100结构体字段的修改:
func main(){ Ptrperson := &Person{ Name:"ywh", Age:23, Birthday:"2021-1-1", Salary:12314124.1231, Skill:"mk", } val := reflect.ValueOf(Ptrperson).Elem() //返回的是reflect.Value类型对象 fmt.Printf("%v\n",val.Interface()) val.Field(0).SetString("hello") val.Field(1).SetInt(111) fmt.Printf("%v\n",val.Interface()) }{ywh 23 2021-1-1 1.2314124e+07 mk} {hello 111 2021-1-1 1.2314124e+07 mk}