本章目录:

  • 0X00 Go语言基础之反射
  • 1.基础介绍
  • 2.类型对象-reflect.Type
  • 3.类型对象值-reflect.Value
  • 4.反射值判断
  • 5.结构体反射实践




0X00 Go语言基础之反射

我们在进行讲解Go反射概念和使用前,先来复习了解变量的内在机制。

Go语言中的变量(Variables)是分为两部分的:

  • 类型信息:预先定义好的元信息。
  • 值信息:程序运行过程中可动态变化的。


反射应用:

  • Json 数据解析
  • ORM 框架工具



1.基础介绍

描述: 反射是指在程序​​运行期​​​对程序本身进行​​访问和修改​​​的能力。即支持反射的语言可以在程序编译期将变量的反射信息,如​​字段名称、类型信息、结构体信息​​等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。


程序运行期说明:

  • 程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。
  • 在运行程序时,程序无法获取自身的信息。


前面我们介绍空接口可以存储任意类型的变量,那我们如何知道这个空接口保存的数据是什么呢?

答: Go程序在运行期使用r eflect包访问程序的反射信息,我们可以利用反射在运行时动态的获取一个变量的类型信息和值信息。
接口类型的变量底层分为两个部分: ​​​动态类型​​​ 和 ​​动态值​​。


reflect包说明

答: 在Go语言的反射机制中,任何接口值都由是一个​​具体类型​​​和​​具体类型的值​​​两部分组成的。在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由​​reflect.Type​​​和​​reflect.Value​​​两部分组成,并且reflect包提供了​​reflect.TypeOf​​​和​​reflect.ValueOf​​两个函数来获取任意对象的Value和Type。


示例1.Go如何做到解析JSON字符串到对象属性中的。


func demo1() {
type person struct {
Id int `json:"id"`
Name string `json:"name"`
}
var p person
strJson := `{"id": 1024,"name": "WeiyiGeek"}`

// 1.反序列化将JSON字符串绑定到对象对应属性之中.(他为何可以根据JSON字符串对应的key解析到对象属性之中)
json.Unmarshal([]byte(strJson), &p)
// 2.输出对象属性值进行验证
fmt.Println("Id = ", p.Id, ",Name = ", p.Name)
}

// # 调用执行结果
Id = 1024 ,Name = WeiyiGeek



2.类型对象-reflect.Type

描述: 在Go语言中,使用​​reflect.TypeOf()​​函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

在反射中关于类型(​​reflect.TypeOf​​​)还划分为两种:​​类型(Type)​​​和​​种类(Kind)​​​,即我们常说的​​reflect.TypeOf​​​返回的t对象如​​t.Name()、 t.Kind()​​方法获取的信息。

  • 在Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。
  • 在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要​​区分指针、结构体、类型别名​​等大品种的类型时,就会用到种类(Kind)。


在reflect包中定义的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 // 底层指针
)


举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。


type myInt int

func reflectType(x interface{}) {
v := reflect.TypeOf(x)
k := v.Kind() // 获取的是反射对象的类型 (注意与下面 reflect.ValueOf(x).Kind() 的区别)
fmt.Printf("Reflect Type = %v, Name Type : %v,Kind : %s (%d)\n", v, v.Name(), k, k)
}

func demo1() {
var a int64 = 100 // 整形
var b float32 = 3.14 // 浮点型
var c rune // 类型别名
var d myInt = 1024 // 自定义类型
type person struct { // 结构体
name string
age int
}
type book struct{ title string }
var e = person{
name: "WeiyiGeek",
age: 18,
}
var f = book{title: "《跟WeiyiGeek学Go语言》"}

// 调用查看反射类型
reflectType(a)
reflectType(b)
reflectType(c)
reflectType(d)
reflectType(e)
reflectType(f)
}

func main(){
demo1()
}

执行结果:


Reflect Type = int64, Name Type : int64,Kind : int64 (6)
Reflect Type = float32, Name Type : float32,Kind : float32 (13)
Reflect Type = int32, Name Type : int32,Kind : int32 (5)
Reflect Type = main.myInt, Name Type : myInt,Kind : int (2)
Reflect Type = main.person, Name Type : person,Kind : struct (25)
Reflect Type = main.book, Name Type : book,Kind : struct (25)



3.类型对象值-reflect.Value

描述: ​​reflect.ValueOf()​​​返回的是​​reflect.Value​​类型,其中包含了原始值的值信息,reflect.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​​ : 将值以字符串类型返回
  • 通过反射设置变量的值。
  • 在函数中通过反射修改变量的值,如果传递非变量地址值则会报​​panic: reflect: reflect.Value.Type using unaddressable value​​错误。

  • 在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值,而反射中使用专有的Elem()方法来获取指针对应的值。


例如,下述我们分别采用反射的ValueOf方法获取的相关信息进行获取值与设置值.


// 通过反射获取变量的值
func reflectTypeValue(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
k := v.Kind() // 获取的是值的种类
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
case reflect.Bool:
// v.Bool()从反射中获取布尔型的原始值,然后通过Bool()强制类型转换
fmt.Printf("type is bool, value is %v\n", bool(v.Bool()))
}
fmt.Printf("Reflect Type = %v, Reflect Value = %v, Name : %v, Kind : %s (%d)\n\n", t, v, t.Name(), v.Kind(), v.Kind())
}

// 通过反射设置变量的值(此种方式会报错)
func reflectSetValue1(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
// 使用默认方式修改的是副本,reflect包会引发panic
if v.Kind() == reflect.Int64 {
v.SetInt(200)
}
fmt.Printf("Type %v, Value %v\n", t, v)
}

// 通过反射设置变量的值
func reflectSetValue2(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
// 反射中使用 Elem()方法获取指针对应的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200)
}
fmt.Printf("Type %v, Value %v\n", t, v)
}

func demo2() {
// 将常规类型初始化并设置值,然后通过反射获取值
var a int = 1024
var b float32 = 3.14
reflectTypeValue(a)
reflectTypeValue(b)

// 将int类型的原始值转换为reflect.Value类型
c := reflect.ValueOf(10)
fmt.Printf("Type c : %T, Value c : %v\n", c, c)
reflectTypeValue(c)

// 通过反射设置变量的值(两种方法)
var d int64 = 65535
// reflectSetValue1(d) //panic: reflect: reflect.Value.SetInt using unaddressable value
reflectSetValue2(&d) // 修改是的指针指向的值
fmt.Println("通过反射设置变量(d)的值: ", d)
}

func main(){
demo2()
}

执行结果:


Reflect Type = int, Reflect Value = 1024, Name : int, Kind : int (2)

type is float32, value is 3.140000
Reflect Type = float32, Reflect Value = 3.14, Name : float32, Kind : float32 (13)

type is bool, value is true
Reflect Type = bool, Reflect Value = true, Name : bool, Kind : bool (1)

Type c : reflect.Value, Value c : 10
Reflect Type = reflect.Value, Reflect Value = <int Value>, Name : Value, Kind : struct (25)

Type *int64, Value 0xc0000ba040
通过反射设置变量(d)的值: 200



4.反射值判断

Go中常用的反射值是否为空以及是否有效常常使用以下两种方法​​isNil()​​​和​​isValid()​​:

  • ​func (v Value) IsNil() bool​​​: 常被用于判断指针是否为空, 返回v持有的值是否为nil,且分类必须是​​通道、函数、接口、映射、指针、切片​​之一;否则IsNil函数会导致panic。
  • ​func (v Value) IsValid() bool​​​: 常被用于判定返回值是否有效, 返回v是否持有一个值。如果v是Value零值会返回假,此时v除了​​IsValid、String、Kind​​之外的方法都会导致panic。。

示例演示:


package main

import (
"fmt"
"reflect"
)

type b struct{}

func (t *b) Demo() int {
fmt.Print("我是通过Call调用的Demo方法,")
return 1024
}

func main() {
// (1) *int类型空指针 : 必须是通道、函数、接口、映射、指针、切片之一
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
// (2) nil值 : 除了IsValid、String、Kind之外的方法都会导致panic。
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())

// (3) 实例化一个匿名结构体
b1 := struct{}{}
b2 := struct {
abc string
}{}

// (4) 实例化一个结构体
b3 := new(b)

// 尝试从结构体中查找"abc"字段
fmt.Println("b1是否存在的结构体成员 abc ? :", reflect.ValueOf(b1).FieldByName("abc").IsValid()) // 不存在
fmt.Println("b2是否存在的结构体成员 abc ? :", reflect.ValueOf(b2).FieldByName("abc").IsValid()) // 存在

// 尝试从结构体中查找"demo"方法
fmt.Println("b1是否存在的结构体方法 Demo ? :", reflect.ValueOf(b1).MethodByName("Demo").IsValid()) //不存在
fmt.Println("b3是否存在的结构体方法 Demo ? :", reflect.ValueOf(b3).MethodByName("Demo").IsValid()) //存在
fmt.Println("b3结构体Demo方法返回值类型: ", reflect.ValueOf(b3).MethodByName("Demo").Call([]reflect.Value{})) //输出执行其方法以及返回值的类型 (特别注意,先执行调用后返回类型,并输出)


// (4) map 尝试从map中查找一个不存在的键
c := map[string]int{}
c["WeiyiGeek"] = 1024
fmt.Println("map中是否存在WeiyiGeek的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("WeiyiGeek")).IsValid())
fmt.Println("map中是否存在Geek的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("Geek")).IsValid())
}

执行结果:


var a *int IsNil: true
nil IsValid: false
b1是否存在的结构体成员 abc ? : false
b2是否存在的结构体成员 abc ? : true
b1是否存在的结构体方法 Demo ? : false
b3是否存在的结构体方法 Demo ? : true
我是通过Call调用的Demo方法,b3结构体Demo方法返回值类型: [<int Value>]
map中是否存在WeiyiGeek的键:true
map中是否存在Geek的键:false



5.结构体反射实践

描述: 下面讲解与结构体相关的反射知识,当任意值通过​​reflect.TypeOf()​​​获得反射对象信息后,如果它的类型是结构体,可以通过​​反射值对象(reflect.Type)​​​的​​NumField()方法​​​和​​Field()方法​​获得结构体成员的详细信息。

结构体成员相关信息获取方法

如下表所示​​reflect.Type​​中与获取结构体成员相关的方法。

方法

说明

NumField() ​​int​

返回结构体成员字段数量。

Field(i int) ​​StructField​

根据索引,返回索引对应的结构体字段的信息。

FieldByName(name string) ​​(StructField, bool)​

根据给定字符串返回字符串对应的结构体字段的信息。

FieldByIndex(index []int) ​​StructField​

多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。

FieldByNameFunc(match func(string) bool) ​​(StructField,bool)​

根据传入的匹配函数匹配需要的字段。

NumMethod() ​​int​

返回该类型的方法集中方法的数目

Method(int) ​​Method​

返回该类型方法集中的第i个方法

MethodByName(string) ​​(Method, bool)​

根据方法名返回该类型方法集中的方法


StructField 类型
描述: StructField类型用来描述结构体中的一个字段的信息,​​​StructField​​ 的定义如下:


type StructField struct {
Name string // 字段的名字。
PkgPath string // 非导出字段的包路径,对导出字段该字段为""。参见 http://golang.org/ref/spec#Uniqueness_of_identifiers
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}


实践案例:
示例说明, 当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息,以及通过索引获取方法信息和调用执行该索引指定的方法。


package main

import (
"fmt"
"reflect"
)

type student struct {
Name string `json:"name" person:"weiyigeek"` // 可以有多个Tag
Score int `json:"score" person:"geek"`
}

// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s student) Study() string {
msg := "[Study] 好好学习,天天向上。"
fmt.Println(msg)
return msg
}

func (s student) Sleep() string {
msg := "[Sleep] 好好睡觉,快快长大。"
fmt.Println(msg)
return msg
}

// 结构体反射示例演示方法
func Reflectstruct(x interface{}) {
// (2) 获取 stu1 对象反射类型信息,输出对象名称以及对象种类
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("reflect.TypeOf ->", t.Name(), t.Kind()) // student struct
fmt.Println("reflect.ValueOf ->", v, v.Kind()) // {WeiyiGeek 90} struct
fmt.Println()

// (3) 通过for循环遍历结构体的所有字段信息(方式1)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
//fmt.Print(v.Field(i).Call([]reflect.Value{}))
fmt.Printf("name:%s index:%d type:%v json tag: %v person tag:%v , Field Anonymous: %v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"), field.Tag.Get("person"), field.Anonymous)
}

// (4) 通过字段名获取指定结构体字段信息(方式2)
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("\nname:%s index:%d type:%v json tag:%v , Field Anonymous: %v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"), scoreField.Anonymous)
}

// (5) 通过for循环遍历结构体的所有方法信息
fmt.Println("reflect.TypeOf NumMethod->", t.NumMethod())
fmt.Println("reflect.ValueOf NumMethod->", v.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:% s,method: %s\n", t.Method(i).Name, methodType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
var args = []reflect.Value{}
// 相当于依次调用结构体中的方法
v.Method(i).Call(args)
}

// (6) 通过方法名获取指定的结构体方法并执行
methodSleep := v.MethodByName("Sleep")
methodSleepType := methodSleep.Type()
fmt.Printf("Reflect Method ptr:%v,method Type: %v\n", methodSleep, methodSleepType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
var args = []reflect.Value{}
// 相当调用 结构体的 Sleep() 方法
methodSleep.Call(args)
}

func main() {
// (1) 实例化 student 结构体
stu := student{
Name: "WeiyiGeek",
Score: 90,
}
Reflectstruct(stu)
}

执行结果:


reflect.TypeOf -> student struct
reflect.ValueOf -> {WeiyiGeek 90} struct

name:Name index:[0] type:string json tag: name person tag:weiyigeek , Field Anonymous: false
name:Score index:[1] type:int json tag: score person tag:geek , Field Anonymous: false

name:Score index:[1] type:int json tag:score , Field Anonymous: false
reflect.TypeOf NumMethod-> 2
reflect.ValueOf NumMethod-> 2
method name:Sleep,method: func() string
[Sleep] 好好睡觉,快快长大。
method name:Study,method: func() string
[Study] 好好学习,天天向上。
Reflect Method ptr:0x4ae080,method Type: func() string
[Sleep] 好好睡觉,快快长大。


反射使用总结:


//# (1) 反射得到传递对象的类型 (类型相关使用)
t := reflect.TypeOf(d)
fmt.Println("参数校验:", t, t.Kind(), t.Elem().Kind()) // # 参数校验 *main.Config ptr struct
for i := 0; i < t.Elem().NumField(); i++ { // # t.Elem() 拿取指针中的元素属性相关信息
field := t.Elem().Field(i) // 遍历单个元素字段信息指定index。
tag := field.Tag.Get("ini") // 获取单个元素字段中 ini tag 属性值
}

//# (2) 反射得到传递对象的值 (值相关使用)
v := reflect.ValueOf(d)
sValue := v.Elem().FieldByName(structName) // 此处反射得到嵌套结构体指定字段名称值信息
sType := sValue.Type() // 此处反射得到嵌套结构体类型信息
for i := 0; i < sValue.NumField(); i++ { // 字段名称值信息也可获取字段数量,同上面一致
field := sType.Field(i) // 反射类型信息中存储了嵌套结构体中的Tag信息
fieldType = field // 反射类型信息中存储了嵌套结构体中的filed信息以供后续值类型判断使用}


至此Go语言反射reflect章节学习完毕!



7.Go语言编程快速入门学习之反射(reflect)章节_go语言