15 反射
15 反射
变量的内在机制
- Go 的变量分为两部分
- 类型信息:预先定义好的元信息
- 值信息:程序运行过程中可动态变化的
反射介绍
- 程序运行时 对程序本身进行访问和修改的能力
- 一般,程序在编译时,变量被转换成内存地址,变量名不会被编译器写道可执行部分
- 在运行程序时,程序无法获取自身的信息
- 但对于支持反射的语言,在程序编译时会将变量的反射信息(字段名、类型信息、结构体信息等)整合到可执行文件中,并给程序提供 接口 访问反射信息
- Go 在运行期间 使用 reflect包 访问程序的反射信息
reflect 包
- 在 Go 的反射机制中,任何接口值 都是由
一个具体类型
和具体类型的值
两部分组成 - 反射相关功能由 内置 reflect 包提供,
- 任意接口值在反射中都可以理解为
reflect.Type
和reflect.Value
两部分, - reflect 包提供了
reflect.TypeOf
reflect.ValueOf
获取任意对象的 Value 和 Type
- 任意接口值在反射中都可以理解为
TypeOf
使用
reflect.TypeOf()
可以获得任意值的类型对象(reflect.Type)
, 程序通过类型对象可以访问任意值的类型信息```go
package mainimport (
"fmt" "reflect"
)
func reflectType(x interface{}) {
v := reflect.TypeOf(x) fmt.Printf("type:%v\n", v)
}
func main() {var a float32 = 3.14 reflectType(a) // type:float32 var b int64 = 100 reflectType(b) // type:int64
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
### type name 和 type kind
* 在反射中,关于类型还划分为两种:`类型Type` 和 `种类Kind`
* `type` 关键字,使我们可以构造很多自定义类型
* `kind` 指底层的类型
* 在反射中,需要区分 指针、结构体等大品种的类型时,会用到`kind`
* 示例:定义 指针类型和结构体类型:
* ```go
package main
import (
"fmt"
"reflect"
)
type myInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
func main() {
var a *float32 // 指针
var b myInt // 自定义类型
var c rune // 类型别名
reflectType(a) // type: kind:ptr
reflectType(b) // type:myInt kind:int64
reflectType(c) // type:int32 kind:int32
type person struct {
name string
age int
}
type book struct{ title string }
var d = person{
name: "沙河小王子",
age: 18,
}
var e = book{title: "《跟小王子学Go语言》"}
reflectType(d) // type:person kind:struct
reflectType(e) // type:book kind:struct
}Go 的反射中,像数组、切片、Map、指针等类型变量,他们的
.Name()
返回空
reflect
包中,定义的 Kind 类型:- ```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 // 底层指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
## ValueOf
* `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 | 将值以字符串类型返回 |
### 通过反射获取值
```go
func reflectValue(x interface{}) {
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()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 100
reflectValue(a) // type is float32, value is 3.140000
reflectValue(b) // type is int64, value is 100
// 将int类型的原始值转换为reflect.Value类型
c := reflect.ValueOf(10)
fmt.Printf("type c :%T\n", c) // type c :reflect.Value
}
- ```go
通过反射设置变量的值
在函数中通过反射修改变量的值,需要传递变量地址才能修改变量值
反射中,使用专有的
Elem()
方法可以获取指针对应的值- ```go
package mainimport (
)func reflectSetValue1(x interface{}) {"fmt" "reflect"
}v := reflect.ValueOf(x) if v.Kind() == reflect.Int64 { v.SetInt(200) //修改的是副本,reflect包会引发panic }
func reflectSetValue2(x interface{}) {
}v := reflect.ValueOf(x) // 反射中使用 Elem()方法获取指针对应的值 if v.Elem().Kind() == reflect.Int64 { v.Elem().SetInt(200) }
func main() {
}var a int64 = 100 // reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value reflectSetValue2(&a) fmt.Println(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
### IsNil() 和 IsValid
#### IsNil()
* 函数原型:`func (v Value) IsNil() bool`
* 判断 v 持有的值是否为`Nil`
* v 持有的值的分类必须是通道、函数、接口、映射、指针、切片之一,
* 否则`IsNil`函数会导致 panic
#### IsValid
* 函数原型:`func (v Value) IsValid() bool`
* 判断 v 是否持有一个值,如果 v 是**Value零值**,返回假,此时v除了IsValid , String, Kind 之外的方法都会导致 panic
#### 示例
* `IsNil` 常用于判断指针是否为空
* `IsValid` 常用于判断返回值是否有效
```go
func main() {
// *int类型空指针
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
// 实例化一个匿名结构体
b := struct{}{}
// 尝试从结构体中查找"abc"字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
// 尝试从结构体中查找"abc"方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
// map
c := map[string]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}
- ```go
结构体反射
与结构体相关的方法
结构体反射示例
反射是把双刃剑
- 反射式一个强大并富有表现力的工具,能使得我们写出灵活的代码
- 基于反射的代码十分脆弱,反射中类型错误会在真正运行的时候才会引发 panic ,那很可能是在代码写完的很长时间之后
- 大量使用反射的代码通常难以理解
- 反射的性能低下
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Comment