Golang中的反射

当不确定调用哪个函数,需要根据传入的参数在运行时决定,就会使用到反射。

当不能明确传入函数的参数类型,需要在运行时处理任意对象时,也会用到反射。

获取值和类型

1
2
3
var num int = 18
fmt.Println(reflect.ValueOf(num)) 获取对象的值 18
fmt.Println(reflect.TypeOf(num)) 获取对象的类型 int
1
2
func ValueOf(i any) Value {} 返回一个Value类型变量
func TypeOf(i any) Type {} 返回一个 Type接口

操作复合类型

对类型的操作通常有这几个

1
2
3
fmt.Println(reflect.TypeOf(user).Kind())        // 类型
fmt.Println(reflect.TypeOf(user).Name()) // 名称
fmt.Println(reflect.TypeOf(user).PkgPath())

需要注意的是

1
2
3
4
5
6
7
// 别名
type U int
var u U = 10
fmt.Println(reflect.TypeOf(u).Name()) // u 类型名称
fmt.Println(reflect.TypeOf(u).Kind()) // int 基础类型
fmt.Println(reflect.TypeOf(&u).Name()) // 指针类型没有名称
fmt.Println(reflect.TypeOf(&u).Kind()) // 指针类型基础类型是 ptr

Type指的是系统原生的数据类型,如int,string等以及使用type关键字定义的类型,这些类型的名称就是类型本身的名称。

Kind指的是对象归属的品种,在reflectKind有如下定义

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
type Kind uint

const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)

获取数组、切片、mapchannel、指针的元素,返回value元素的类型,或者指针指向的类型。

1
2
3
4
a := [2]string{}
fmt.Println(reflect.TypeOf(a).Elem().Name()) // string
m := map[int]string{}
fmt.Println(reflect.TypeOf(m).Elem().Kind()) // string

结构体

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
Name string `json:"name,omitempty" name:"myname"`
Age int `json:"age,omitempty"`
float float64 `json:"float,omitempty"`
p *int `json:"p,omitempty"`
s []string `json:"s,omitempty"`
m map[int]string `json:"m,omitempty"`
desc struct {
desc1 string `json:"desc_1,omitempty"`
desc2 bool `json:"desc_2,omitempty"`
} `json:"desc"`
}

便利结构体

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
a := 100
user := User{
Name: "mitaka",
Age: 18,
float: 188.1,
p: &a,
s: []string{"a", "b"},
m: map[int]string{1: "a", 2: "b"},
desc: struct {
desc1 string `json:"desc_1,omitempty"`
desc2 bool `json:"desc_2,omitempty"`
}(struct {
desc1 string
desc2 bool
}{
desc1: "this is a desc1",
desc2: true,
}),
}
reflectOfUser := reflect.TypeOf(user)
size := reflectOfUser.NumField() // 获取类型的数量
for i := 0; i < size; i++ {
field := reflectOfUser.Field(i) // 获取具体类型
fmt.Printf("name: %s,type: %s,tag: %s\n", field.Name, field.Type, field.Tag) // 打印名称、类型、tag
}

输出

1
2
3
4
5
6
7
name: Name,type: string,tag: json:"name,omitempty" name:"myname"
name: Age,type: int,tag: json:"age,omitempty"
name: float,type: float64,tag: json:"float,omitempty"
name: p,type: *int,tag: json:"p,omitempty"
name: s,type: []string,tag: json:"s,omitempty"
name: m,type: map[int]string,tag: json:"m,omitempty"
name: desc,type: struct { desc1 string "json:\"desc_1,omitempty\""; desc2 bool "json:\"desc_2,omitempty\"" },tag: json:"desc"

可以通过类型断言,抓取对应元素,以及获取元素的某一个tag

1
2
3
4
if f, ok := reflectOfUser.FieldByName("Age"); ok {
fmt.Printf("name: %s,type: %s,tag: %s\n", f.Name, f.Type, f.Tag)
fmt.Println(f.Tag.Get("json"))
}
1
2
name: Age,type: int,tag: json:"age,omitempty"
age,omitempty

需要注意,反射容错性很低,如果tag设置错误,会导致反射无法获取tag

1
Age   int            `json :"age,omitempty"` // 例如引号左边多余一个空格

反射的定律

反射三定律:

  • 反射可以将“接口类型变量”转换为“反射类型变量”

​ 反射类型为reflect.Typeofreflect.Valueof

例如

1
2
3
4
var x float64 = 3.1415
fmt.Println(reflect.ValueOf(x)) // 获取变量
fmt.Println(reflect.ValueOf(x).Kind() == reflect.Float64) // true 变量是float64类型
fmt.Println(reflect.ValueOf(x).Int()) // 将变量转成其他类型,reflect: call of reflect.Value.Int on float64 Value
  • 反射可以将“反射类型对象”转换为“接口类型变量”
1
2
3
4
5
type Myint int                        // Myint类型
var x Myint = 10
v := reflect.ValueOf(x) // 获取值
y := v.Interface() // 转换为接口类型变量
fmt.Printf("%v %T\n", y, y) // 10 main.Myint

判断反射类型变量是否可变更,以及变更

1
2
3
4
5
6
var x float64 = 3.14
v := reflect.ValueOf(&x) // 需要传入指针,才能修改变量的值
fmt.Println(v.CanSet()) // false 无法更改
fmt.Println(v.Elem().CanSet()) // true 可修改
v.Elem().SetFloat(1.11) // 将Elem的值改成对应类型的值
fmt.Println(x) // 1.11
  • 如果要修改反射类型对象的值,取决于其值是否可取地址

判断是否可以取地址

1
2
3
4
5
6
7
8
9
x := 10
a := reflect.ValueOf(10)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := reflect.ValueOf(&x).Elem()
fmt.Println(a.CanAddr()) // false
fmt.Println(b.CanAddr()) // false
fmt.Println(c.CanAddr()) // false
fmt.Println(d.CanAddr()) // true

修改结构体中的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
type T struct {
Name string // 元素首字母大写,能够导出
Age int
}
var t T = T{"mitaka", 18}
v := reflect.ValueOf(&t).Elem() // 获取可修改对象
v.Field(0).SetString("abc") // index 0 代表 Name
v.Field(1).SetInt(123) // index 1 代表 Age
tType := v.Type()
s := v.NumField()
for i := 0; i < s; i++ {
fmt.Println(tType.Field(i).Name, v.Field(i).Kind(), v.Field(i).Interface())
}

结果

1
2
Name string abc
Age int 123

反射灵活和性能

通过反射,可以修改结构体成员中的变量,这个过程可不需要知道结构体成员是什么,非常的灵活。但是在实际使用中,这样的使用方式,会非常消耗性能

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
// 通过结构体直接修改
func BenchmarkChangeStruct(b *testing.B) {
t := T{S: "abc"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
t.S = "def"
}
}

// 反射,通过结构体成员序号修改
func BenchmarkReflectStruct(b *testing.B) {
t := T{S: "abc"}
v := reflect.ValueOf(&t).Elem()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.Field(0).SetString("def")
}
}

// 反射,通过成员名称搜索元素修改
func BenchmarkReflectStructByName(b *testing.B) {
t := T{S: "abc"}
v := reflect.ValueOf(&t).Elem()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.FieldByName("S").SetString("abc")
}
}

结果

1
2
3
4
5
6
BenchmarkChangeStruct
BenchmarkChangeStruct-10 1000000000 0.3236 ns/op
BenchmarkReflectStruct
BenchmarkReflectStruct-10 353290569 3.259 ns/op
BenchmarkReflectStructByName
BenchmarkReflectStructByName-10 23579730 51.30 ns/op

通过结构体赋值和反射赋值,性能相差十倍,通过元素名称检索性能更差。

反射调用函数

函数作为一等公民,反射同样可以作用在函数上

1
2
3
4
5
6
7
8
9
10
11
func foo(v int) int {
return v + 10
}

func main() {
fmt.Println(foo(1))

v := reflect.ValueOf(foo)
values := v.Call([]reflect.Value{reflect.ValueOf(2)})
fmt.Println(values[0])
}

输出

1
2
11
12

同样可以使用性能测试,测试直接调用函数的性能和通过反射调用函数,相比之下,通过反射性能较低。

反射的有效判断

通过反射获取value,可以进一步判断是零值还是空值。

IsNil:返回是否为nil。如果值类型不是channel、函数、接口、map、指针、切片,类似语言层的v == nil操作

isValid:判断值是否有效。当值本身非法时,返回false,例如reflect Value不包含任何值,值为nil

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var a *int
fmt.Println(reflect.ValueOf(a).IsNil()) // true a 没有初始化内存 nil
var b *int = new(int)
fmt.Println(reflect.ValueOf(b).IsNil()) // false b有内存地址

var c []int
fmt.Println(reflect.ValueOf(c).IsNil()) // true c 没有初始化内存
var d []int = make([]int, 0)
fmt.Println(reflect.ValueOf(d).IsNil()) // false

var e int
fmt.Println(reflect.ValueOf(e).IsZero()) // true e == 0
var f int = 10
fmt.Println(reflect.ValueOf(f).IsZero()) // false f != 0

var g int8 = 10
fmt.Println(reflect.ValueOf(g).IsValid()) // true 判断值是否越界,-128~127
var h struct{} = struct{}{}
fmt.Println(reflect.ValueOf(h).FieldByName("12").IsValid()) // false
fmt.Println(reflect.ValueOf(h).MethodByName("123").IsValid()) // false

var i map[interface{}]interface{}
fmt.Println(reflect.ValueOf(i).MapIndex(reflect.ValueOf("abc")).IsValid()) // false

反射调用接口体方法

通过reflect.Method可以调用结构体的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type User struct {
}

func (u User) Print(s string) {
fmt.Println("print", s)
}

func (u User) Run(i int) {
fmt.Println("run", i)
}

func main() {
u := User{}
v := reflect.ValueOf(&u)
fmt.Println(v.NumMethod()) 2 // 获取个数
v.Method(0).Call([]reflect.Value{reflect.ValueOf("abc")}) // 排序按照ASCII码排序
v.Method(1).Call([]reflect.Value{reflect.ValueOf(123)})
}

反射在序列化和反序列中的应用

MarshalUnmarshal的函数中,就是通过反射实现,核心代码如下

/usr/local/go/src/encoding/json/encode.go

1
2
3
4
5
func (e *encodeState) marshal(v any, opts encOpts) (err error) {
... // 省略部分代码
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}

/usr/local/go/src/encoding/json/decode.go

1
2
3
4
5
6
7
func (d *decodeState) unmarshal(v any) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidUnmarshalError{reflect.TypeOf(v)}
}
... // 省略部分代码
}

由于使用反射,所以性能会有损失,可以使用easyjson进行序列化和反序列化

1
go get github.com/mailru/easyjson && go install github.com/mailru/easyjson/...@latest

对于结构体,生成对应描述文件

1
easyjson -all <file>.go

序列化

1
2
someStruct := &SomeStruct{Field1: "val1", Field2: "val2"}
rawBytes, err := easyjson.Marshal(someStruct)

反序列化

1
2
someStruct := &SomeStruct{}
err := easyjson.Unmarshal(rawBytes, someStruct)

性能对比

image-20221023165832912

结构体转map

首先看一般方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type User struct {
Name string
Age int
}

func main() {
u := User{
Name: "mitaka",
Age: 18,
}
b, _ := json.Marshal(u)
u1 := map[string]interface{}{}
json.Unmarshal(b, &u1)
for k, v := range u1 {
fmt.Printf("k: %s,v: %+v,%T\n", k, v, v)
}
}

输出结果

1
2
k: Name,v: mitaka,string
k: Age,v: 18,float64

可以看到,结构体中的int类型,反序列化之后,变成float64

通过反射可以解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func tomap(s interface{}) (map[string]interface{}, error) {
out := make(map[string]interface{})
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr { // 判断传入的类型
v = v.Elem() // 将值转化为结构体中的值
}
if v.Kind() != reflect.Struct {
return out, errors.New("input must be struct or struct pointer")
}
t := v.Type() // 将类型转化成结构体
num := v.NumField()
for i := 0; i < num; i++ { // 便利类型的值
key := t.Field(i).Name
vi := v.Field(i).Interface()
out[key] = vi
}
return out, nil
}

输出:

1
2
k: Name,v: mitaka,string
k: Age,v: 18,int

或者通过某个tag实现转换

1
2
3
4
5
6
type User struct {
Name string `test:"name"`
Age int `test:"age"`
F float32 `test:"f"`
I int8 `test:"i"`
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func tomap(in interface{}, tag string) (map[string]interface{}, error) {
out := make(map[string]interface{})
t := reflect.TypeOf(in)
v := reflect.ValueOf(in)
if t.Kind() == reflect.Pointer {
v = v.Elem()
}
t = v.Type()
if t.Kind() != reflect.Struct {
return out, errors.New("input must be struct or struct pointer")
}
for i := 0; i < t.NumField(); i++ {
key := t.Field(i).Tag.Get(tag)
value := v.Field(i).Interface()
out[key] = value
}
return out, nil
}

输出结果

1
2
3
4
k: f,v: 1.123,float32
k: i,v: 120,int8
k: name,v: mitaka,string
k: age,v: 18,int

或者使用现成的方案

https://github.com/fatih/structs

引申阅读:结构体转map的若干方法