Go part 7 反射,反射类型对象,反射值对象
反射
反射是指在程序运行期间对程序本身进行访问和修改的能力,(程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时,程序无法获取自身的信息)
支持反射的语言可以在程序编译期间将变量的反射信息,如字段名称、类型等信息整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获取类型的反射信息,并且有能力修改它们
Go 程序在运行期间使用 reflect 包访问程序的反射信息
像 Python,JavaScript 类动态语言,由于本身的语法特性就可以让代码运行期间访问程序自身的值和类型信息,因此不需要反射系统
反射类型对象(reflect.Type)
使用 reflect.TypeOf() 函数可以获取变量的反射类型对象(reflect.Type)
typeOfTest := reflect.TypeOf(test)
通过反射类型对象获取类型信息
通过反射类型对象可以获取自身的类型信息,使用 Name() 方法获取类型名称,使用 Kind() 方法获取类型归属的种类
package main
import (
"fmt"
"reflect"
)
func GetReflectInfo(a interface{}) {
// 获取变量 a 的类型对象,类型对象的类型是 reflect.Type
var typeOfA reflect.Type = reflect.TypeOf(a)
fmt.Printf("%T\n", typeOfA)
// 打印类型名 和 种类
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
func main() {
GetReflectInfo("666")
}
运行结果:
*reflect.rtype
string string
发现类型和种类都是 string,很奇怪是不是,接着看下面的例子 ...
理解反射类型对象的类型(Type)和种类(Kind)
编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)
1)类型(Type)
类型指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字自定义的类型,自定义类型需要指定类型名称,例如,type A struct {},类型名就是 A
2)种类(Kind)
种类指的是对象归属的大品种,在 reflect 包中有如下定义:
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 // 底层指针
)
Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr
type A struct{} 结构体属于 Struct 种类,*A 属于 Ptr 种类
3)从自定义的类型对象中获取类型名称和类型种类(加强理解)
package main
import (
"fmt"
"reflect"
)
type Cat struct{}
type Enum int
func main(){
var cat Cat = Cat{}
var num Enum = 1
//对 cat 变量使用反射
var typeOfCat reflect.Type= reflect.TypeOf(cat)
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
//对 num 变量使用反射
typeOfNum := reflect.TypeOf(num)
fmt.Println(typeOfNum.Name(), typeOfNum.Kind())
}
运行结果:
Cat struct
Enum int
通过反射类型对象获取指针指向的元素类型
对指针类型的变量获取反射类型对象后,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个过程被称之为取元素,等效于对指针类型变量做了一个 * 操作
dmeo:cat 指针变量的反射类型对象的类型名称是 空字符串(为什么不是 *cat),种类是 ptr,获取值之后的类型名是 cat,种类是 struct
package main
import (
"fmt"
"reflect"
)
type Cat struct {}
func GetPointerReflectInfo(a interface{}) {
//获取指针类型的反射类型对象
typeOfA := reflect.TypeOf(a)
fmt.Printf("NameType:%T Name:%v Kind:%v\n", typeOfA.Name(), typeOfA.Name(), typeOfA.Kind())
//取指针类型的元素
typeOfA = typeOfA.Elem()
fmt.Printf("Name:%v Kind:%v\n", typeOfA.Name(), typeOfA.Kind())
}
func main(){
//创建 cat 类型的指针实例
var cat *Cat = new(Cat)
GetPointerReflectInfo(cat)
}
运行结果:
NameType:string Name: Kind:ptr
Name:Cat Kind:struct
通过反射类型对象获取结构体字段的类型信息
变量通过 reflect.TypeOf() 函数获取反射类型对象后,如果反射类型对象的种类是 struct,那么可以通过反射类型对象(reflect.Type)的 NumField() 和 Field() 等方法获得结构体字段的详细信息,以结构体字段类型(StructField)返回
种类是 struct 的反射类型对象(reflect.Type)可以通过调用下面的方法来获取:
| 方法 | 说明 |
|---|---|
| Field(i int) StructField | 根据索引,返回索引对应的StructField。当值不是结构体或索引超界时发生宕机 |
| NumField() int | 返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机 |
| FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的StructField。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机 |
| FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回StructField。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机 |
| FieldByNameFunc( match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机 |
结构体字段类型(StructField)也是一个结构体种类,里面包含的字段有 Name、PkgPath、Type 等,StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
demo:实例化一个结构体,并给字段赋值,然后获取该结构体变量的反射类型对象(reflect.Type),然后调用 FieldByName() 方法查找结构体中指定 Name 的字段,最后直接获取到了字段的结构体数据
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
Type int `json:"type" id:"100"`
}
func main(){
//创建猫的结构体实例
var cat Cat = Cat{"tom", 66}
//获取反射类型对象
typeOfCat := reflect.TypeOf(cat)
//通过索引遍历结构体字段
for i:=0; i<typeOfCat.NumField(); i++ {
var catField reflect.StructField = typeOfCat.Field(i)
fmt.Printf("%v, %v\n", catField.Name, catField.Tag)
}
//通过 Type 字段查找字段信息
catTagField, ok := typeOfCat.FieldByName("Type")
if ok {
//从 Tag 字段中通过 Get() 方法获取到指定的 tag,没有取到默认为 空 string
fmt.Printf("'%v', '%v', '%v'\n", catTagField.Tag.Get("json"), catTagField.Tag.Get("id"), catTagField.Tag.Get("nil"))
}
}
运行结果:
Name,
Type, json:"type" id:"100"
'type', '100', ''
反射值对象(reflect.Value)
反射不仅可以获取变量的类型信息,还可以动态的获取 和 修改变量的值
使用 reflect.ValueOf() 函数得到变量的反射值对象(reflect.Value),进而获取 和 修改变量的值
valueOfTest := reflect.ValueOf(test)
从反射值对象中获取值
可以通过下面几种方法从反射值对象(reflect.Value)中获取值,如下表:
| 方法名 | 说 明 |
|---|---|
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
| Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
| Bool() bool | 将值以 bool 类型返回 |
| Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
| String() string | 将值以字符串类型返回 |
demo:首先获取到反射值对象(reflect.Value),然后通过两种方式获取值,一个是调用 反射值对象的 Interface() 方法,这个方法返回的是 interface{} 类型,需要经过类型断言获取值,另外一个是直接调用反射值对象的 Int() 方法,获取到 int64 的值
package main
import (
"fmt"
"reflect"
)
func GetReflectValue(a interface{}) {
var valueOfA reflect.Value = reflect.ValueOf(a)
fmt.Printf("%v, %T\n", valueOfA.Interface(), valueOfA.Interface())
//通过类型断言,将 interface{} 转换为类型
var getA int = valueOfA.Interface().(int)
//反射值对象直接调用 Int() 获取64位的值
var getInt64A int64 = valueOfA.Int()
fmt.Printf("%v, %v\n", getA, getInt64A)
}
func main() {
var num int = 1024
GetReflectValue(num)
//var str string = "hello"
//GetReflectValue(str)
}
运行结果:
1024, int
1024, 1024
但如果 a 变量是 string,会触发 panic,是不合理的,目前认为需要做断言判断处理
通过反射值对象获取指针指向的元素的值
对指针类型变量获取反射值对象后,可以通过 Elem() 方法获取这个指针指向的元素类型,这个过程被称之为取元素,等效于对指针类型变量做了一个 * 操作
demo:cat 指针变量的反射值对象的值是一个指针类型的结构体,获取元素(*)之后的值是一个空的结构体,然后通过 FieldByName() 方法获取到 Name 的值,并设置 Name 的值
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
func GetPointerReflectValue(cat interface{}) {
valueOfCat := reflect.ValueOf(cat)
fmt.Printf("%T %v\n", valueOfCat, valueOfCat)
//获取指针类型的元素
valueOfCat = valueOfCat.Elem()
//打印指针内元素的类型,发现是一样的
fmt.Printf("%T %v\n", valueOfCat, valueOfCat)
//打印结构体元素中 Name 字段的值
fmt.Printf("'%v'\n", valueOfCat.FieldByName("Name"))
//拿到结构体元素中 Name 字段的值
valueFieldName := valueOfCat.FieldByName("Name")
//设置值,再次打印结构体元素中 Name 字段的值,设置成功了
valueFieldName.SetString("johny")
fmt.Printf("'%v'\n", valueOfCat.FieldByName("Name"))
}
func main(){
var cat *Cat = new(Cat)
GetPointerReflectValue(cat)
}
运行结果:
reflect.Value &{}
reflect.Value {}
''
'johny'
通过反射值对象访问结构体字段的值信息
变量通过 reflect.ValueOf() 函数获取反射值对象(reflect.Value)后,反射值对象提供访问结构体的方法,通过这些方法可以访问到结构体中任意字段的值
| 方 法 | 备 注 |
|---|---|
| Field(i int) Value | 根据索引,返回索引对应的结构体字段的反射值对象。当值不是结构体或索引超界时发生宕机 |
| NumField() int | 返回结构体字段数量。当值不是结构体或索引超界时发生宕机 |
| FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回无效的值<invalid reflect.Value>,当值不是结构体或索引超界时发生宕机 |
| FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
| FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机 |
demo:下面构造了一个结构体包含不同类型的成员,通过上面提供的反射值对象的方法,可以获取到结构体的值信息
package main
import (
"fmt"
"reflect"
)
type Person struct {
a int
b string
float64
bool
next *Person
}
func GetStructReflectValue(person interface{}) {
//对结构体进行反射(值包装结构体)
var valueOfPerson reflect.Value = reflect.ValueOf(person)
//遍历反射值对象
for i := 0; i < valueOfPerson.NumField(); i++ {
personField := valueOfPerson.Field(i) //根据索引获取字段值信息
//输出字段值的类型
fmt.Println(personField.Type())
}
//根据名字查找字段
personFieldA := valueOfPerson.FieldByName("a")
fmt.Println(personFieldA.Type())
//多层成员访问,根据 []int 提供的索引查找字段
personFieldInner := valueOfPerson.FieldByIndex([]int{4, 2})
fmt.Println(personFieldInner.Type(), personFieldInner.Float())
}
func main() {
var person Person = Person{next: &Person{}}
GetStructReflectValue(person)
}
运行结果:
int
string
float64
bool
*main.Person
int //根据名字查找字段的值类型
float64 0 //多层成员访问,得到的字段的值类型
多层成员访问中:[]int{4,2} 中的 4 表示,在 Person 结构体中索引值为 4 的成员,也就是 next。next 的类型为 Person,也是一个结构体,因此使用 []int{4,2} 中的 2 继续在 next 值的基础上索引,结构为 Person 中索引值为 2 的匿名字段 float64,所以打印的类型为 float64,初始值是 0
思考:获取结构体字段的值有个前提,是需要知道字段的类型,然后调用对应的方法,比如字段是 float64类型,那么我可以使用 Float() 方法获取,要是不知道呢?那是否需要做分支判断了?
判断反射值的 Nil 和 Valid
反射值对象(reflect.Value)提供两个方法进行 空值 和 有效性 的判断
| 方 法 | 说 明 | |
|---|---|---|
| IsNil() bool | 返回值是否为 nil。如果变量类型不是 channel、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作 |
|
| IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect.Value不包含任何值,值为 nil 等。 | |
IsNil() 常被用于判断指针是否为空;IsValid() 常被用于判定返回值是否有效,使用好这两个方法,可以规避 panic
通过反射值对象修改值
使用反射值对象(reflect.Value)修改值,需要遵循一些规则,如果没有按照规则编码代码,轻则无法修改成功,重则程序在运行时会发生宕机
修改值分两步,首先调用方法取到指针或地址,然后调用修改方法
1)通过反射值对象(reflect.Value)获取指针、地址的方法见下表:
| 方法名 | 备 注 |
|---|---|
| Elem() Value | 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value |
| Addr() Value | 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 |
| CanAddr() bool | 表示值是否可寻址 |
| CanSet() bool | 返回值能否被修改。要求值可寻址且是导出的字段,返回 false 时,仍然修改值时会发生宕机 |
2)通过反射值对象(reflect.Value)修改值的方法见下表:
| Set(x Value) | 将值设置为传入的反射值对象的值 |
|---|---|
| Setlnt(x int64) | 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机 |
| SetUint(x uint64) | 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机 |
| SetFloat(x float64) | 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机 |
| SetBool(x bool) | 使用 bool 设置值。当值的类型不是 bool 时会发生宕机 |
| SetBytes(x []byte) | 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机 |
| SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机 |
3)值可以被修改的条件之一:可被寻址
简单来说,就是参数必须传递指针类型,实例代码如下:
package main
import (
"fmt"
"reflect"
)
func ModifyReflectValue(a interface{}) {
valueOfA := reflect.ValueOf(a)
valueOfA.SetInt(666)
}
func main() {
var num int = 1024
ModifyReflectValue(num)
fmt.Println(num)
}
运行结果:
panic: reflect: reflect.Value.SetInt using unaddressable value
报错是说:SetInt 正在使用一个不能被寻址的值,是因为传入的是 a 的值,而不是 a 的地址,修改一下,传入 a 的指针就好了
package main import ( "fmt" "reflect" ) func ModifyReflectValue(a interface{}) { valueOfA := reflect.ValueOf(a) //先取出 a 地址的值 valueA := valueOfA.Elem() //设置 a 的值 valueA.SetInt(666) } func main() { var num int = 1024 ModifyReflectValue(&num) fmt.Println(num) } 运行结果: 666
4)值可以被修改的条件之二:可被导出
结构体成员中,如果字段不能被导出,就不能被修改,代码如下:
package main
import (
"fmt"
"reflect"
)
type Dog struct {
name string
}
func ModifyReflectValue(dog interface{}) {
valueOfDog := reflect.ValueOf(dog)
//先取到 dog 实例地址的元素
valueOfDog = valueOfDog.Elem()
//通过名称取到 name 字段的值信息
valueFieldName := valueOfDog.FieldByName("name")
//设置 a 的值
valueFieldName.SetString("二哈")
}
func main() {
var dog *Dog = &Dog{"xiaobai"}
ModifyReflectValue(dog)
fmt.Println(dog.name)
}
运行结果:
panic: reflect: reflect.Value.SetString using value obtained using unexported field
报错是说:SetString 正在使用值来自一个未导出的字段,为了能修改这个值,需要该字段支持被导出,即 name 首字母需大写 Name,让反射可以访问,正确代码如下:
package main import ( "fmt" "reflect" ) type Dog struct { Name string } func ModifyReflectValue(dog interface{}) { valueOfDog := reflect.ValueOf(dog) //先取到 dog 实例地址的元素 valueOfDog = valueOfDog.Elem() //通过名称取到 name 字段的值信息 valueFieldName := valueOfDog.FieldByName("Name") //设置 a 的值 valueFieldName.SetString("二哈") } func main() { var dog *Dog = &Dog{"xiaobai"} ModifyReflectValue(dog) fmt.Println(dog.Name) } 运行结果: 二哈
创建反射类型对象
无法创建反射值对象
当已知反射类型对象(reflect.Type)时,可以动态的创建这个类型的实例,类型为指针类型,例如 reflect.Type 的类型名为 Cat 时,创建 Cat 的指针,即 *Cat,代码如下:
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
func CreateReflectObject(cat interface{}){
//获取反射类型对象
typeOfCat := reflect.TypeOf(cat)
fmt.Printf("'%v' '%v'\n", typeOfCat.Name(), typeOfCat.Kind()) //typeOfCat.Type undefined
//如果没有获取元素,下面会直接 New 反射类型对象的指针
typeOfCat = typeOfCat.Elem()
fmt.Printf("'%v' '%v'\n", typeOfCat.Name(), typeOfCat.Kind()) //typeOfCat.Type undefined
//手动创建反射类型对象,实例的类型为指针
newTypeOfCat := reflect.New(typeOfCat)
fmt.Printf("%v %v\n", newTypeOfCat.Type(), newTypeOfCat.Kind())
//获取指针所指向的元素
newTypeOfCat = newTypeOfCat.Elem()
//打印元素的类型和种类
fmt.Printf("%v %v\n", newTypeOfCat.Type(), newTypeOfCat.Kind())
//fmt.Printf("%v %v\n", typeOfCatCopy.Name(), typeOfCatCopy.Kind()) //typeOfCatCopy.Name undefined
}
func main(){
var cat *Cat = new(Cat)
CreateReflectObject(cat)
}
运行结果:
'' 'ptr'
'Cat' 'struct'
*main.Cat ptr
main.Cat struct
反射值对象调用函数
当反射值对象(reflect.Value)中的值类型为函数时,可以使用反射值对象调用该函数,使用时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过反射值对象切片([]reflect.Value)返回
demo:下面的代码中声明一个加法函数,传入两个整型值,返回两个整型值的和,将函数保存在反射值对象(reflect.Value)中,然后将两个整型的值构造成反射值对象切片([]reflect.Value),调用 Call() 方法,返回也是反射值对象切片([]reflect.Value)
package main
import (
"fmt"
"reflect"
)
func add(a, b int) int {
return a + b
}
func main(){
//将函数包装为反射值对象
valueOfAdd := reflect.ValueOf(add)
//构造函数参数,传入两个整型的值
var paramSlice []reflect.Value = []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200)}
//调用函数,返回值也是 []reflect.Value
result := valueOfAdd.Call(paramSlice)
fmt.Println(result[0].Int())
}
运行结果:
300
反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用
end ~

浙公网安备 33010602011771号