Golang学习 - 反射(reflect)简单使用
hanpy

Go 中的反射是用 reflect 包实现,reflect 包实现了运行时的反射能力,能够让程序操作不同的对象。反射是 Go 语言很重要的一个特性

反射的两种基本类型

反射包中有两对非常重要的函数和类型

方法名 描述
reflect.TypeOf 可以获得任意值的类型对象,返回类型: reflect.Type
reflect.ValueOf 可以获得任意值的值对象,返回类型: reflect.Value

这两个函数的参数都是空接口 interface{} ,内部存储了即将被反射的变量。

可以将 reflect.Value 看作反射的值,reflect.Type 看作反射的实际类型。其中,reflect.Type是一个接口,包含和类型有关的许多方法签名。reflect.Value 是一个结构体,其内部包含了很多方法。

reflect.TypeOf

reflect.Type 接口具体方法列表

常用方法

方法 描述
Kind() Kind 返回该变量的的具体分类
Name() string 返回该类型在自身包内的类型名,如果是未命名类型会返回””
NumField() int 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将 panic
Field(i int) StructField 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会 panic
Implements(u Type) bool 如果该类型实现了u代表的接口,会返回真
Len() int 返回array类型的长度,如非数组类型将 panic
Elem() Type 返回该类型的元素类型,如果该类型的Kind不是 ArrayChanMapPtrSlice,会 panic
Key() Type 返回map类型的键的类型。如非映射类型将 panic

常见的两个结构体和类型

StructField 类型描述结构体中的一个字段的信息。

1
2
3
4
5
6
7
8
9
10
11
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 // 是否匿名字段
}

StructTag 是结构体字段的标签。

1
2
3
4
type StructTag string

// Get方法返回标签字符串中键key对应的值。如果标签中没有该键,会返回""。如果标签不符合标准格式,Get的返回值是不确定的。
func (tag StructTag) Get(key string) string

简单使用

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
package main

import (
"fmt"
"reflect"
)

type User struct {
Name string
Age int
}

func main() {
user := User{"zhangsan", 30}

// 通过反射的方式获取user的类型信息
userTypeOf := reflect.TypeOf(user)
// Kind:返回该变量的的具体分类
fmt.Printf("%v \n", userTypeOf.Kind().String()) // struct
// Name 返回该类型在自身包内的类型名,如果是未命名类型会返回""
fmt.Println(userTypeOf.Name()) // User
fmt.Printf("user有%d个字段\n", userTypeOf.NumField()) // user有2个字段
fmt.Printf("user的第一个字段的类型是是%v\n", userTypeOf.Field(0).Name)

arr := [...]int{1, 2, 3, 4, 5}
arrTypeOf := reflect.TypeOf(arr)
// 返回array类型的长度,如非数组类型将 panic
fmt.Printf("数组arr的长度是%d\n", arrTypeOf.Len()) // 数组arr的长度是5
// 返回该类型的元素类型,如果该类型的Kind不是 `Array`、`Chan`、`Map`、`Ptr` 或 `Slice`,会 panic
fmt.Printf("数组arr的类型是%s\n", arrTypeOf.Elem().Kind().String()) // 数组arr的类型是int

m := map[string]string{
"name": "zhangsan",
}
mTypeOf := reflect.TypeOf(m)
// 返回map类型的键的类型。如非映射类型将 panic
fmt.Printf("Map m的key的类型是%s\n", mTypeOf.Key().Kind().String()) // Map m的key的类型是string
}

reflect.Value

reflect.ValueOf() 获取数据信息,返回 Value 类型。Value被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法

常用方法

方法名 描述
Field(i int) StructField 返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
FieldByName(name string) Value 返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段)
Index(i int) Value 返回第 i 个元素,主要用于遍历,不能越界。
前提类型是Array, Slice, String之一,
Set(x Value) 通过反射修改值。
Type() Type 获取值的类型

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func ValueOf() {
user := User{"hanpy", 32}

userValueOf := reflect.ValueOf(user)
// 返回struct 类型的第 i 个字段的值,不是struct 会 panic,越界也会panic
fmt.Printf("结构体user第1个字段值是: %v\n", userValueOf.Field(0)) // 结构体user第1个字段值: hanpy
// 根据属性名获取值
fmt.Printf("结构体user字段%s的值是: %v\n", "Name", userValueOf.FieldByName("Name")) // 结构体user字段Name的值是: hanpy

strSlice := []string{"你", "我", "他", "!"}
strSliceValOf := reflect.ValueOf(strSlice)
fmt.Printf("打印字符串第%d个元素值v: %v\n", 0, strSliceValOf.Index(0)) // 打印字符串第0个元素值v: 你
fmt.Printf("打印字符串第%d个元素值v: %v\n", 1, strSliceValOf.Index(1)) // 打印字符串第1个元素值v: 我
fmt.Printf("打印字符串第%d个元素值v: %v\n", 3, strSliceValOf.Index(2)) // 打印字符串第3个元素值v: 他

// 修改值
num := 10
numValOf := reflect.ValueOf(&num) //注意这里需要传地址
fmt.Printf("修改前的值: %v \n", num)
numValOf.Set(reflect.ValueOf(100))
fmt.Printf("修改后的值: %v \n", num)
}