# 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
- 第一章:基础知识
- 1.1 一文搞定开发环境的搭建
- 1.2 五种变量创建的方法
- 1.3 数据类型:整型与浮点型
- 1.4 数据类型:byte、rune与字符串
- 1.5 数据类型:数组与切片
- 1.6 数据类型:字典与布尔类型
- 1.7 数据类型:指针
- 1.8 流程控制:if-else
- 1.9 流程控制:switch-case
- 1.10 流程控制:for 循环
- 1.11 流程控制:goto 无条件跳转
- 1.12 流程控制:defer 延迟语句
- 1.13 流程控制:理解 select 用法
- 1.14 异常机制:panic 和 recover
- 1.15 语法规则:理解语句块与作用域
- 第二章:面向对象
- 2.1 面向对象:结构体与继承
- 2.2 面向对象:接口与多态
- 2.3 面向对象:结构体里的 Tag 用法
- 2.4 学习接口:详解类型断言
- 2.5 学习接口:Go 语言中的空接口
- 2.6 学习接口:接口的三个"潜规则"
- 2.7 学习反射:反射三定律
- 2.8 学习反射:全面学习反射的函数
- 2.9 详细图解:静态类型与动态类型
- 2.10 关键字:make 和 new 的区别?
- 第三章:项目管理
- 3.1 依赖管理:包导入很重要的 8 个知识点
- 3.2 依赖管理:超详细解读 Go Modules 应用
- 3.3 开源发布:如何开源自己写的包给别人用?
- 3.4 代码规范:Go语言中编码规范
- 第四章:并发编程
- 4.1 学习 Go 函数:理解 Go 里的函数
- 4.2 学习 Go 协程:goroutine
- 4.3 学习 Go 协程:详解信道/通道
- 4.4 学习 Go 协程:WaitGroup
- 4.5 学习 Go 协程:互斥锁和读写锁
- 4.7 学习 Go 协程: 信道死锁经典错误案例
- 4.7 学习 Go 协程:如何实现一个协程池?
- 4.8 理解 Go 语言中的 Context
- 4.9 学习一些常见的并发模型
- 第五章:学标准库
- 5.1 fmt.Printf 方法详解
- 5.2 os/exec 执行命令的五种姿势
- 第六章:开发技能
- 6.1 Go 命令:go test 工具详解
- 6.2 单元测试:如何进行单元测试?
- 6.3 调试技巧:使用 GDB 调试 Go 程序
- 6.4 Go 命令: Go 命令指南
- 第七章:暂未分类
- 7.1 20 个学习 Go 语言的精品网站
