GoLang反射机制深入讲解
作者:alwaysrun
反射
Go语言提供了reflect 包来访问程序的反射信息;定义了两个重要的类型Type和Value:
- reflect.TypeOf:获取任意值的类型对象(reflect.Type);
- reflect.ValueOf:获得值的反射值对象(reflect.Value);
反射类型Type
Go语言程序中的类型(Type)指的是系统原生数据类型(如 int、string、bool、float32 等),以及使用 type 关键字定义的类型;而反射种类(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.Elem() 方法获取这个指针指向的元素类型(等效于对指针类型变量做了一个*
操作)。
func reflectStruct() { type Cat struct { } aCat := &Cat{} // 获取结构体实例的反射类型对象 typeOfCat := reflect.TypeOf(aCat) fmt.Printf("ptr 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()) typeOfCat = reflect.TypeOf(*aCat) fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind()) } // ptr name:'' kind:'ptr' // element name: 'Cat', element kind: 'struct' // *ptr name:'Cat' kind:'struct'
结构体
对结构体对象,获取对象信息后,可通过NumField() 和 Field() 方法获得结构体成员的详细信息。
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引返回索引对应字段的信息 |
NumField() int | 返回结构体成员字段数量 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段 |
字段信息中含有:
type StructField struct { Name string // 字段名 PkgPath string // 字段在结构体中的路径 Type Type // 字段的反射类型 reflect.Type Tag StructTag // 字段的结构体标签 Offset uintptr // 字段在结构体中的相对偏移 Index []int // FieldByIndex中的索引顺序 Anonymous bool // 是否为匿名字段 }
获取字段的名称与tag:
func reflectStructField() { // 声明一个空结构体 type Cat struct { Name string // 带有结构体tag的字段 Type int `json:"type" id:"100"` } aCat := Cat{Name: "mimi", Type: 1} // 获取结构体实例的反射类型对象 typeOfCat := reflect.TypeOf(aCat) // 遍历结构体所有成员 for i := 0; i < typeOfCat.NumField(); i++ { fieldType := typeOfCat.Field(i) fmt.Printf("Field-%v: name: %v tag: '%v'\n", i, fieldType.Name, fieldType.Tag) } // 通过字段名, 找到字段类型信息 if catType, ok := typeOfCat.FieldByName("Type"); ok { fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id")) } } // Field-0: name: Name tag: '' // Field-1: name: Type tag: 'json:"type" id:"100"' // Field Tag: type 100
反射值Value
通过下面几种方法从反射值对象 reflect.Value 中获取原值
方法名 | 说 明 |
---|---|
Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
Bool() bool | 将值以 bool 类型返回 |
Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
String() string | 将值以字符串类型返回 |
通过反射获取变量的值:
func reflectValue() { var a int = 1024 // 获取变量a的反射值对象 valueOfA := reflect.ValueOf(a) // 获取interface{}类型的值, 通过类型断言转换 var getA int = valueOfA.Interface().(int) // 获取64位的值, 强制类型转换为int类型 var getA2 int = int(valueOfA.Int()) fmt.Println(getA, getA2) } // 1024 1024
结构体
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问:
方 法 | 备 注 |
---|---|
Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象 |
NumField() int | 返回结构体成员字段数量 |
FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段,没有找到时返回零值 |
FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值; 没有找到时返回零值 |
FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段,没有找到时返回零值 |
空与有效性判断
反射值对象(reflect.Value)提供一系列方法进行零值和空判定:
方 法 | 说 明 |
---|---|
IsNil() bool | 是否为 nil,只对通道、函数、接口、map、指针或切片有效(否则会panic) |
IsValid() bool | 是否有效,当值本身非法时(不包含任何值,或值为 nil),返回 false |
修改值
通过反射修改变量值的前提条件之一:这个值必须可以被寻址,简单地说就是这个变量必须能被修改。结构体成员中,如果字段没有被导出,即便也可以被访问,也不能通过反射修改。
方法名 | 备 注 |
---|---|
Elem() Value | 取值指向的元素值(类似于*操作);对指针或接口时发生panic |
Addr() Value | 对可寻址的值返回其地址(类似于&操作);当值不可寻址时发生panic |
CanAddr() bool | 表示值是否可寻址 |
CanSet() bool | 返回值能否被修改;要求值可寻址且是导出的字段 |
修改结构体字段的值(需要结构体地址,与导出字段):
func reflectModifyValue() { type Dog struct { LegCount int } // 获取dog实例地址的反射值对象 valueOfDog := reflect.ValueOf(&Dog{}) // 取出dog实例地址的元素 valueOfDog = valueOfDog.Elem() vLegCount := valueOfDog.FieldByName("LegCount") vLegCount.SetInt(4) fmt.Println(vLegCount.Int()) }
函数调用
如果反射值对象(reflect.Value)为函数,可以通过Call()调用:参数使用反射值对象的切片[]reflect.Value构造后传入,返回值通过[]reflect.Value返回。
func add(a, b int) int { return a + b } func reflectFunction() { // 将函数包装为反射值对象 funcValue := reflect.ValueOf(add) // 构造函数参数, 传入两个整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射调用函数 retList := funcValue.Call(paramList) // 获取第一个返回值, 取整数值 fmt.Println(retList[0].Int()) }
反射三定律
官方提供了三条定律来说明反射:
- 反射可将interface类型变量转换成反射对象;
- 反射可将反射对象还原成interface对象;
- 要修改反射对象,其值必须是可写的(反射其指针类型);
var x float64 = 3.4 v := reflect.ValueOf(x) // v is reflext.Value var y float64 = v.Interface().(float64) fmt.Println("value:", y) // 3.4
值类型不能直接修改,可通过传递地址并通过Elem获取后修改:
var x float64 = 3.4 v := reflect.ValueOf(&x) v.Elem().SetFloat(7.8) fmt.Println("x :", v.Elem().Interface()) // 7.8
interface
interface是Go实现抽象的一个非常强大的工具;当向接口赋值时,接口会存储实体的类型信息;反射就是通过接口的类型信息实现的。
interface类型是一种特殊类型,代表方法集合;可存放任何实现了其方法的值(实际存放的是(value,type)
对)。reflect包中实现了反射的各种函数:
- 提取interface的value的方法
reflect.ValueOf()->reflect.Value
; - 提取interface的type的方法
reflect.TypeOf()->reflect.Type
;
空interface类型(interface{}
)的方法集为空,所以可认为任何类型都实现了该接口;因此其可存放任何值。
底层结构
interface底层结构分为iface和eface描述接口,其区别是eface为不包含任何方法的空接口。
iface
iface定义如下:
tab
指向一个itab
实体的指针:表示接口的类型(赋给此接口的实体类型);data
指向接口具体的值:一般而言是一个指向堆内存的指针。
type iface struct { tab *itab data unsafe.Pointer }
itab结构:
_type
字段描述了实体的类型:包括内存对齐方式,大小等;inter
字段则描述了接口的类型;fun
字段放置是实体类中和接口方法对应(实体中其他方法不在此处)的方法地址,以实现接口调用方法的动态分派;一般在每次给接口赋值发生转换时会更新此表。
type itab struct { inter *interfacetype _type *_type link *itab hash uint32 bad bool inhash bool unused [2]byte fun [1]uintptr }
iface结构全貌图:
eface
eface结构:只维护了一个 _type
字段,表示空接口所承载的具体的实体类型。
type eface struct { _type *_type data unsafe.Pointer }
到此这篇关于GoLang反射机制深入讲解的文章就介绍到这了,更多相关Go反射内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!