ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 2.8 学习反射:全面学习反射的函数 ## 1. 获取类别:Kind() Type 对象 和 Value 对象都可以通过 Kind() 方法返回对应的接口变量的基础类型。 ```go reflect.TypeOf(m).Kind() reflect.ValueOf(m).Kind() ``` 在这里,要注意的是,Kind 和 Type 是有区别的,Kind 表示更基础,范围更广的分类。 有一个例子来表示, iPhone (接口变量)的 Type 是手机,Kind 是电子产品。 通过查看源码文件: `src/reflect/type.go` ,可以得知 Kind 表示的基本都是 Go 原生的基本类型(共有 25 种的合法类型),而不包含自定义类型。 ```go 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 // 底层指针 ) ``` 下面来看一下 Kind 函数如何使用? 第一种:传入值 ```go package main import ( "fmt" "reflect" ) type Profile struct { name string age int gender string } func main() { m := Profile{} t := reflect.TypeOf(m) fmt.Println("Type: ",t) fmt.Println("Kind: ",t.Kind()) } ``` 输出如下 ```go Type: main.Profile Kind: struct ``` 第二种:传入指针,关于 Elem() 的使用上一篇文章已经讲过了,它会返回 Type 对象所表示的指针指向的数据。 ```go package main import ( "fmt" "reflect" ) type Profile struct { name string age int gender string } func main() { m := Profile{} t := reflect.TypeOf(&m) fmt.Println("&m Type: ",t) fmt.Println("&m Kind: ",t.Kind()) fmt.Println("m Type: ",t.Elem()) fmt.Println("m Kind: ",t.Elem().Kind()) } ``` 输出如下 ```go &m Type: *main.Profile &m Kind: ptr m Type: main.Profile m Kind: struct ``` 如果这里不使用 TypeOf 方法,而是使用 ValueOf 方法呢,应该这样子写 ```go package main import ( "fmt" "reflect" ) type Profile struct { name string age int gender string } func main() { m := Profile{} v := reflect.ValueOf(&m) fmt.Println("&m Type: ",v.Type()) fmt.Println("&m Kind: ",v.Kind()) fmt.Println("m Type: ",v.Elem().Type()) fmt.Println("m Kind: ",v.Elem().Kind()) } ``` ## 2. 进行类型的转换 ### Int() :转成 int 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var age int = 25 v1 := reflect.ValueOf(age) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.Int() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: 25 转换后, type: int64, value: 25 ``` ### Float():转成 float 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var score float64 = 99.5 v1 := reflect.ValueOf(score) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.Float() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: 99.5 转换后, type: float64, value: 99.5 ``` ### String():转成 string 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var name string = "Go编程时光" v1 := reflect.ValueOf(name) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.String() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: Go编程时光 转换后, type: string, value: Go编程时光 ``` ### Bool():转成布尔值 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var isMale bool = true v1 := reflect.ValueOf(isMale) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.Bool() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: true 转换后, type: bool, value: true ``` ### Pointer():转成指针 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var age int = 25 v1 := reflect.ValueOf(&age) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.Pointer() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: 0xc0000b4008 转换后, type: uintptr, value: 824634458120 ``` ### Interface():转成接口类型 由于空接口类型可以接收任意类型的值,所以上面介绍的各种方法,其实都可以用 Interface() 函数来代替。 区别只有一个,使用 Interface() 返回的对象,静态类型为 interface{},而使用 Int ()、String() 等函数,返回的对象,其静态类型会是 int,string 等更具体的类型。 关于 Interface() 示例代码如下 ```go package main import ( "fmt" "reflect" ) func main() { var age int = 25 v1 := reflect.ValueOf(age) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) v2 := v1.Interface() fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: 25 转换后, type: int, value: 25 ``` ## 3. 对切片的操作 ### Slice():对切片再切片(两下标) Slice() 函数与上面所有类型转换的函数都不一样,它返回还是 reflect.Value 反射对象,而不再是我们所想的真实世界里的切片对象。 通过以下示例代码可验证 ```go package main import ( "fmt" "reflect" ) func main() { var numList []int = []int{1,2} v1 := reflect.ValueOf(numList) fmt.Printf("转换前, type: %T, value: %v \n", v1, v1) // Slice 函数接收两个参数 v2 := v1.Slice(0, 2) fmt.Printf("转换后, type: %T, value: %v \n", v2, v2) } ``` 输出如下 ```go 转换前, type: reflect.Value, value: [1 2] 转换后, type: reflect.Value, value: [1 2] ``` ### Slice3():对切片再切片(三下标) Slice3() 与 Slice() 函数一样,都是对一个切片的反射对象 ### Set() 和 Append():更新切片 示例代码如下 ```go package main import ( "fmt" "reflect" ) func appendToSlice(arrPtr interface{}) { valuePtr := reflect.ValueOf(arrPtr) value := valuePtr.Elem() value.Set(reflect.Append(value, reflect.ValueOf(3))) fmt.Println(value) fmt.Println(value.Len()) } func main() { arr := []int{1,2} appendToSlice(&arr) fmt.Println(arr) } ``` 输出如下 ```go 3 [1 2 3] [1 2 3] ``` ## 4. 对属性的操作 ### NumField() 和 Field() ```go package main import ( "fmt" "reflect" ) type Person struct { name string age int gender string } func (p Person)SayBye() { fmt.Println("Bye") } func (p Person)SayHello() { fmt.Println("Hello") } func main() { p := Person{"写代码的明哥", 27, "male"} v := reflect.ValueOf(p) fmt.Println("字段数:", v.NumField()) fmt.Println("第 1 个字段:", v.Field(0)) fmt.Println("第 2 个字段:", v.Field(1)) fmt.Println("第 3 个字段:", v.Field(2)) fmt.Println("==========================") // 也可以这样来遍历 for i:=0;i<v.NumField();i++{ fmt.Printf("第 %d 个字段:%v \n", i+1, v.Field(i)) } } ``` 输出如下 ```go 字段数: 3 第 1 个字段: 写代码的明哥 第 2 个字段: 27 第 3 个字段: male ========================== 第 1 个字段:写代码的明哥 第 2 个字段:27 第 3 个字段:male ``` ## 5. 对方法的操作 ### NumMethod() 和 Method() 要获取 Name ,注意使用使用 TypeOf ```go package main import ( "fmt" "reflect" ) type Person struct { name string age int gender string } func (p Person)SayBye() { fmt.Println("Bye") } func (p Person)SayHello() { fmt.Println("Hello") } func main() { p := &Person{"写代码的明哥", 27, "male"} t := reflect.TypeOf(p) fmt.Println("方法数(可导出的):", t.NumMethod()) fmt.Println("第 1 个方法:", t.Method(0).Name) fmt.Println("第 2 个方法:", t.Method(1).Name) fmt.Println("==========================") // 也可以这样来遍历 for i:=0;i<t.NumMethod();i++{ fmt.Printf("第 %d 个方法:%v \n", i+1, t.Method(i).Name) } } ``` 输出如下 ```go 方法数(可导出的): 2 第 1 个方法: SayBye 第 2 个方法: SayHello ========================== 第 1 个方法:SayBye 第 2 个方法:SayHello ``` ## 10. 动态调用函数(使用索引且无参数) 要调用 Call,注意要使用 ValueOf ```go package main import ( "fmt" "reflect" ) type Person struct { name string age int } func (p Person)SayBye() string { return "Bye" } func (p Person)SayHello() string { return "Hello" } func main() { p := &Person{"wangbm", 27} t := reflect.TypeOf(p) v := reflect.ValueOf(p) for i:=0;i<v.NumMethod();i++{ fmt.Printf("调用第 %d 个方法:%v ,调用结果:%v\n", i+1, t.Method(i).Name, v.Elem().Method(i).Call(nil)) } } ``` 输出如下 ```go 调用第 1 个方法:SayBye ,调用结果:[Bye] 调用第 2 个方法:SayHello ,调用结果:[Hello] ``` ## 11. 动态调用函数(使用函数名且无参数) ```go package main import ( "fmt" "reflect" ) type Person struct { name string age int gender string } func (p Person)SayBye() { fmt.Print("Bye") } func (p Person)SayHello() { fmt.Println("Hello") } func main() { p := &Person{"写代码的明哥", 27, "male"} v := reflect.ValueOf(p) v.MethodByName("SayHello").Call(nil) v.MethodByName("SayBye").Call(nil) } ``` ## 12. 动态调用函数(使用函数且有参数) ```go package main import ( "fmt" "reflect" ) type Person struct { } func (p Person)SelfIntroduction(name string, age int) { fmt.Printf("Hello, my name is %s and i'm %d years old.", name, age) } func main() { p := &Person{} //t := reflect.TypeOf(p) v := reflect.ValueOf(p) name := reflect.ValueOf("wangbm") age := reflect.ValueOf(27) input := []reflect.Value{name, age} v.MethodByName("SelfIntroduction").Call(input) } ``` 输出如下 ``` Hello, my name is wangbm and i'm 27 years old. ``` ## 13. 如何看待反射? **反射** 提供了一些在早期高级语言中难以实现的运行时特性 - 可以在一定程度上避免硬编码,提供灵活性和通用性。 - 可以作为一个[第一类对象](https://zh.wikipedia.org/wiki/第一類物件)发现并修改源代码的结构(如代码块、类、方法、协议等)。 - 可以在运行时像对待源代码语句一样动态解析字符串中可执行的代码(类似JavaScript的eval()函数),进而可将跟class或function匹配的字符串转换成class或function的调用或引用。 - 可以创建一个新的语言字节码解释器来给编程结构一个新的意义或用途。 ### 劣势 - 此技术的学习成本高。面向反射的编程需要较多的高级知识,包括框架、关系映射和对象交互,以实现更通用的代码执行。 - 同样因为反射的概念和语法都比较抽象,过多地滥用反射技术会使得代码难以被其他人读懂,不利于合作与交流。 - 由于将部分信息检查工作从编译期推迟到了运行期,此举在提高了代码灵活性的同时,牺牲了一点点运行效率。 通过深入学习反射的特性和技巧,它的劣势可以尽量避免,但这需要许多时间和经验的积累。 ## 几点说明 1. 有 reflect 的代码一般都较难理解,使用时请注意适当。 2. Golang 的反射很慢,这个和它的 API 设计有关 3. 反射是一个高级知识点,内容很多,不容易掌握,应该小心谨慎的使用它 4. 不到不得不用的地步,能避免使用反射就不用。 在开发中,你或许会碰到在有些情况下,你需要获取一个对象的类型,属性及方法,而这个过程其实就是 **反射**。 通过反射你可以实现一些动态的功能,提高了 Go 作为一门静态语言的灵活性。 Go 原生为我们内置了一个 reflect 包来为对象提供反射能力,本篇文章将重点于这个 reflect 包的使用。 ## 参考文章 [Go 系列教程 ——第 34 篇:反射](https://mp.weixin.qq.com/s/dkgJ_fA0smvpv69t5Nv-7A) [第001节:反射reflect](https://www.qfgolang.com/?special=fanshejizhi) https://golang.org/pkg/reflect/ https://segmentfault.com/a/1190000016230264 https://studygolang.com/articles/12348?fr=sidebar https://blog.golang.org/laws-of-reflection