什么是反射
反射是指在程序运行期间对程序本身进行访问和修改的能力。
1.1 反射的类型对象(reflect.Type)
在Go程序中,使用reflect.Typeof()函数可以获得任意值得类型对象,程序通过类型对象可以访问任意值得类型信息。
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(),typeOfA.Kind())
}
代码输出如下:
int int
通过reflect.TypeOf()取得变量a的类型对象typeOfA,类型为reflect.Type()。通过typeOfA类型对象的成员函数,可以分别获取到typeOfA变量得类型名称为int,种类名称(kind)为int。
1.1.1 理解反射的类型(Type)与种类(kind)
(1) 反射种类(kind)的定义
Go程序中的类型(Type)指的是系统原生数据类型,如int、string、bool、float32等类型,以及使用type关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用type A struct{}定义结构体时,A就是struct{}的类型。
种类(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 // 底层指针
)
1.1.2从类型对象中获取类型名称和种类
获取类型名称type使用的是reflect.Type中的Name()方法,返回表示类型名称的字符串。
类型归属种类kind使用的是reflect.Type中的Kind()方法,返回是reflect.kind类型中的常量。
package main
import (
"fmt"
"reflect"
)
type Enum int
const (
Zero Enum = 0
)
func main() {
type cat struct {
}
typeOfCat := reflect.TypeOf(cat{})
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
typeOfA := reflect.TypeOf(Zero)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
代码输出如下:
cat struct
Enum int
1.1.3 指针与指针指向的元素
对指针获取反射对象时,可以通过reflect.Elem()方法获取这个指针指向的元素类型。
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
}
ins := new(cat)
typeOfCat := reflect.TypeOf(ins)
fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
typeOfCat = typeOfCat.Elem()
fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
}
代码输出如下:
name:'' kind:'ptr'
name:'cat' kind:'struct'
1.1.4使用反射获取结构体的成员类型
| 方法 | 说明 |
|---|---|
| Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
| NumField() int | 返回结构体成员字段数量。 |
| FieldByName(name string)(StructField,bool) | 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时返回false。 |
| FieldByIndex(index []int) StructField | 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值 |
| FieldByNameFunc(match func(string) bool)(StructField, bool) | 根据匹配函数匹配需要的字段 |
获取成员反射信息
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
Name string
Type int `json:"type" id:"100"`
}
ins := cat{Name: "mimi", Type: 1}
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取一个成员
fieldType := typeOfCat.Field(i)
//获取成员的名字和标签
fmt.Printf("name:'%v' tag:'%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
输出结果:
name:'Name' tag:''
name:'Type' tag:'json:"type" id:"100"'
type 100
1.2反射的值对象(reflect.Value)
Go语言中使用reflect.Value获取和设置变量得值。使用reflect.ValueOf()函数获得值得反射值对象(reflect.Value)。
1.2.1 从反射值对象(reflect.Value)中获取值得方法
通过以下方法从反射值对象中获取原始值。
| 方法名 | 说明 |
|---|---|
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
| Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
| Bool() bool | 将值以 bool 类型返回 |
| Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
| String() string | 将值以字符串类型返回 |
1.2.2获取值对象的例子——使用反射访问结构体的成员字段的值
反射值对象(reflect.Value)提供了对结构体访问的方法,通过这些方法可以完成对结构体任意值得访问。
| 方法 | 说明 |
|---|---|
| Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
| NumField() int | 返回结构体成员字段数量。 |
| FieldByName(name string)(StructField,bool) | 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时返回false。 |
| FieldByIndex(index []int) StructField | 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值 |
| FieldByNameFunc(match func(string) bool)(StructField, bool) | 根据匹配函数匹配需要的字段 |
(1)访问变量的值
package main
import (
"fmt"
"reflect"
)
type dummy struct {
a int
b string
next *dummy
}
func main() {
v := reflect.ValueOf(dummy{
a: 3,
b: "hello",
next: &dummy{},
})
// 获取字段数量
numFields := v.NumField()
fmt.Println("NumField", numFields)
//获取索引为1的字段
fieldB := v.Field(1)
//获取值
getFieldB := fieldB.String()
fmt.Println("fieldB value:", getFieldB)
//根据名字查找字段
fmt.Println("FieldByName:", v.FieldByName("b").Type())
//根据索引查找值
fmt.Println("FieldByIndex:", v.FieldByIndex([]int{0}).Type())
//根据索引查找值,[]int{2,1}中的2表示,在dummy结构中索引值为2的成员,也就next。next的类型为dummy,也是一个结构体,因此使用[]int{2,1}
//中的1继续在next的基础上索引,结构为dummy中的索引值为1的b字段,类型为string。
fmt.Println("FieldByIndex:", v.FieldByIndex([]int{2, 1}).Type())
}
输出结果:
NumField 3
fieldB value: hello
FieldByName: string
FieldByIndex: int
FieldByIndex: string
(2)修改变量的值
修改值得相关的方法
| Set(x Value) | 将值设置为传入的反射值对象的值 |
|---|---|
| SetInt(x int64) | 使用int64设置值。 |
| SetUint(x uint64) | 使用int64设置值。 |
| SetFloat(x float64) | 使用float64设置值。 |
| SetBool(x bool) | 使用bool设置值。 |
| SetString(x string) | 设置字符串值。 |
注意:
1.值可以被修改的条件之一:可被寻址
2.值可以被修改的条件之二:可被导出
package main
import (
"fmt"
"reflect"
)
type dog struct {
LegCount int
}
func main() {
dog := &dog{}
valueOfDog := reflect.ValueOf(dog)
//
valueOfDog = valueOfDog.Elem()
//
vLegCount := valueOfDog.FieldByName("LegCount")
vLegCount.SetInt(4)
//
fmt.Println(dog.LegCount)
}
输出结果:
4