go语言:反射
1. 反射的应用
1.1 反射简介
- go语言通过reflect包提供运行时反射机制
- 静态类型interface{}、Type类型、Value类型
1.2 反射应用场景
- go语言API库封装。 Fmt.Printf
- go框架开发。
- 结构体标签的实现。
1.3 反射的基本使用
-
在运行时,动态的获取变量信息。值、类型、类别
-
在运行时,获取结构体的信息。字段名、方法
-
在运行时,修改变量指,调用方法。
2. 反射的原理
变量 – reflect.TypeOf() – reflect.Type 类型 – interface{kind() Field() NumField() … }
变量 – reflect.ValueOf() – reflect.Value 类型 – interface{kind() Type() Elem() Field() NumField() … }
3. 反射的实际编程使用
- 变量 —— 空接口传参 —— interface —— reflect.ValueOf() —— reflect.Value类型 —— Interface() 方法—— interface —— 类型断言 —— 变量
- 变量、interface 、reflect.Value类型 可以相互转换。
3.1 基础数据类型的类型转换
func reflectForint(i interface{}) {
// 获取reflect.Value 类型
reVal := reflect.ValueOf(i)
// n := reVal + 100 // 不能使用value类型与int类型运算
// fmt.Printf("%T, %v\n", reVal, reVal)
// reflect.Value 类型 -- interface{}
iVal := reVal.Interface()
fmt.Printf("%T, %v\n", iVal, iVal)
// n := iVal + 10 // 不能使用interface类型与int类型运算
// 解决办法:使用类型断言将interface转换成int
val := iVal.(int)
n := val + 10
fmt.Println(n)
}
func main() {
// 定义基础类型变量
var num int = 10
// 变量 --> interface{}
reflectForint(num)
}
3.2 结构体类型的转换
type Student struct {
Name string
Id int
Addr string
}
func ReflectForStruct(i interface{}) {
// interface -> reflect.Value
reVal := reflect.ValueOf(i)
reType := reflect.TypeOf(i)
fmt.Printf("type=%v, val=%v\n", reType, reVal)
// reflect.Value --> interface{}
iVal := reVal.Interface()
// 类型断言 interface{} --> struct
// 只能用下面的val来索引成员
if val, ok := iVal.(Student); ok {
fmt.Printf("val=%v, iVal=%v, reVal=%v, val.Name=%v\n",
val, iVal, reVal, val.Name)
}
}
func main() {
stu := Student{"alin", 2017002297, "北京"}
// 从变量 --> interface{}
ReflectForStruct(stu)
}
3.3 不使用类型断言进行类型转换
使用reflect.Kind()函数
func (v Value) Kind() Kind
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
func reflectTest(i interface{}) {
// 获取reflect.Value
reVal := reflect.ValueOf(i)
if reVal.Kind() == reflect.Int {
n := reVal.Int() + 100
fmt.Println("判断是一个int, n=", n)
}
if reVal.Kind() == reflect.Bool {
fmt.Println("判断是一个bool, ", reVal.Bool())
}
if reVal.Kind() == reflect.Float64 {
fmt.Println("判断是一个Float64, ", reVal.Float())
}
if reVal.Kind() == reflect.String {
fmt.Println("判断是一个String, ", reVal.String()+"haha")
}
}
func main() {
var a int = 10
var f float64 = 3.12
var str string = "hoho"
var bl bool = true
reflectTest(a)
reflectTest(f)
reflectTest(str)
reflectTest(bl)
}
3.4 kind和type区别
-
kind:类别。较type而言更大的范畴,更官方的范畴。
-
type:(不是reflect.Type,是数据类型)具体类型。较kind而言表示的范畴更具体
type Student4 struct {
Name string
Id int
Addr string
}
func reflectForKind(i interface{}) {
fmt.Printf("%T\n", i)
reVal := reflect.ValueOf(i)
fmt.Printf("%v\n", reVal.Kind())
}
func main() {
// var num int = 10
var stu Student4
reflectForKind(stu)
}
输出结果:
main.Student4
struct
3.5 反射修改变量值
- 传参时需要传递地址值(引用)。 eg:
reflectForSetInt(&num)
- 使用reflect.Value 类型的 SetXxx() 方法修改对应类型值。
- 必须借助 reflect.Value.Elem()来获取Value类型对应的变量的封装。有点像"解引用"。
func reflectForSetInt(iptr interface{}) {
// 获取变量对应的reflect.Value
reVal := reflect.ValueOf(iptr)
fmt.Println("kind1:", reVal.Kind())
// fmt.Printf("%T\n", iptr)
fmt.Println("kind2:", reVal.Elem().Kind())
if reVal.Kind() == reflect.Ptr && reVal.Elem().Kind() == reflect.Int {
reVal.Elem().SetInt(666)
}
}
func main() {
var num int = 10
reflectForSetInt(&num)
fmt.Println("num=", num)
}
4. 反射操作结构体
-
定义带Tag的结构体类型
-
初始化结构体
test := {xxx, xxx, ...}
-
获取reflect.Type类型和reflect.Value类型
-
循环获取结构体字段的信息
-
获取字段个数,使用NumField()方法
-
循环获取每一个字段的信息。 – 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 // 是否匿名字段 }
type StructTag string func (tag StructTag) Get(key string) string // 传入key值,获取tag的value
-
获取字段名 structField.Name
-
获取字段类型 structField.Type
-
获取字段值 reVal.Field(i)
-
const tagName = "Testing"
type Test struct {
Name string `Testing:"-"` // 序列化结果中没有这个属性
Age int `Testing:"age,omitempty"`
Id int `Testing:"idx,string"`
Sex string `Testing:"sex"`
}
func main() {
test := Test{"AAA", 18, 1001, "male"}
reType := reflect.TypeOf(test)
reVal := reflect.ValueOf(test)
for i:=0; i<reType.NumField(); i++ {
// 获取每一个字段的结构体描述信息StructField
StructField := reType.Field(i)
// 获取tag的值
tagVal := StructField.Tag.Get(tagName)
fmt.Printf("%d. %v(%v):%v, TAG:`%v`\n",
i + 1, StructField.Name, StructField.Type, reVal.Field(i), tagVal)
}
}
// 输出结果
1. Name(string):AAA, TAG:`-`
2. Age(int):18, TAG:`age,omitempty`
3. Id(int):1001, TAG:`idx,string`
4. Sex(string):male, TAG:`sex`
5. 反射使用欧冠结构体Tag
- 创建结构体
- 初始化对象 test, 传参(&test)
- 封装实现函数
- 获取reflect.Type,借助kind判断是否传参为ptr,进一步判断是否为struct
- 获取reflect.Value类型,使用Elem()方法得到Value对象reVal
- for 获取字段,以NumField()为循环上限
- 处理输出的标签名
- 获取结构体字段信息structField := reVal.Type().Field()
- 有tag标签
- 获取structField成员 – StructTag
- 根据已知的tag的key(“label”),获取对应的tag的value值。 Get()方法
- 没有tag标签的
- 判断Get()方法的返回值,如果为""
- 使用字段名,拼接标签名。structField.Name+":"
- 处理输入的数据值
- 取出字段的数据值保存成字符串。 Sprintf()/reVal.Field(i)
- 判断字段类型为string进一步处理if structField.Type.Kind() == reflect.String
- 进一步判断类型为string的字段的tag的key为"uppercase"的value是否为"true"
- 根据判断结果,使用stings.Toupper()、string.ToLower()
- 拼接标签名、数据值输出
type Person struct {
Name string `label:"Person Name: " uppercase:"true"`
Age int `label:"Age is: "`
Sex string `label:"Sex is: "`
Description string
}
func myMarshal(iptr interface{}) {
// 获取Type
reType := reflect.TypeOf(iptr)
// 容错
if reType.Kind() != reflect.Ptr || reType.Elem().Kind() != reflect.Struct {
fmt.Println("输入的数据非指针类型")
return
}
// 获取value
reVal := reflect.ValueOf(iptr).Elem()
// 获取字段信息及对应值
for i:=0; i<reVal.NumField(); i++ {
// fmt.Println(reVal.Field(i))
// 获取字段信息 StructField
structField := reVal.Type().Field(i)
// 获取Tag成员
tag := structField.Tag
// 获取tag 中key对应的value值
tagValue := tag.Get("label")
// 判断如果tagValue为空,使用字段名作为tagValue
if tagValue == "" {
tagValue = structField.Name + ":"
}
// fmt.Println(tagValue)
// 获取字段的数据值
dataVal := fmt.Sprintf("%v", reVal.Field(i))
// fmt.Println(dataVal)
// 判断是否是string
if structField.Type.Kind() == reflect.String {
// 进一步判断是否tag中的uppercase的值为true
if tag.Get("uppercase") == "true" {
// 将对应的数据值转大写
dataVal = strings.ToUpper(dataVal)
} else {
dataVal = strings.ToLower(dataVal)
}
}
// 拼接输出
fmt.Println(tagValue+dataVal)
}
}
func main() {
person := Person{"alin", 21, "Male", "COOL"}
myMarshal(&person)
}
// 输出结果
Person Name: ALIN
Age is: 21
Sex is: male
Description:cool
6. 反射调用结构体方法
- 获取方法的个数
func (v Value) NumMethod() int
- 指定方法。参数从0开始计数。方法名:按ASCII码排序
func (v Value) Method(i int) Value
- 调用Method指定的方法。
func (v Value) Call(in []Value) []Value
- 参数:传递给要调用的那个方法的参数
- 返回值:要调用的方法的返回值。通过方法的下标(从0开始)指定。
type Student2 struct {
Id int `json:"index"`
Name string `json:"name"`
Age int `json:"学员年龄"`
Score float32
}
func (stu Student2) Print() {
fmt.Println("------------stu:", stu)
}
// 注意:方法名,首字母必须大写。否则 reflect.Value 的 Method 找不到该方法。
func (stu Student2) Zdd(a, b int) int {
return a + b
}
func (stu Student2) ResetInfo(id int, name string, age int, score float32) {
stu.Id = id
stu.Name = name
stu.Age = age
stu.Score = score
}
func useReflect(ref interface{}) {
reType := reflect.TypeOf(ref)
reVal := reflect.ValueOf(ref)
reKind := reVal.Kind()
if reKind != reflect.Struct {
fmt.Println("Kind err!")
return
}
numField := reVal.NumField()
fmt.Println("Student 结构体包含字段数为:", numField)
for i:=0; i<numField; i++{
tagVal := reType.Field(i).Tag.Get("json")
if tagVal != "" {
fmt.Printf("%d. %v (%v) : %v, Tag : '%v'\n",
i+1, reType.Field(i).Name, reType.Field(i).Type, reVal.Field(i), tagVal)
} else {
fmt.Printf("%d. %v (%v) : %v\n",
i+1, reType.Field(i).Name, reType.Field(i).Type, reVal.Field(i))
}
}
numMethod := reVal.NumMethod()
fmt.Printf("Student 结构体含有 %d 个方法。\n", numMethod)
// 调用一个方法 --- print
reVal.Method(0).Call(nil)
var params []reflect.Value
params = append(params, reflect.ValueOf(123))
params = append(params, reflect.ValueOf(543))
// 调用一个方法 --- Add
sum := reVal.Method(2).Call(params)
fmt.Println("sum =", sum[0].Int())
}
func main() {
stu := Student2{9527, "Andy", 18, 90.5}
useReflect(stu)
}