Golang 反射

Golang语言中reflect反射机制。详细原文:地址

在go中,反射是建立在类型基础之上,每一个变量都会有一个静态类型。

接口值到反射对象

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"reflect"
)

func main() {
var x int = 1
fmt.Println("type: ", reflect.TypeOf(x))
}
1
type:  int

TypeOf函数的定义如下,参数为接口类型,返回值为类型

1
func TypeOf(i interface {}) Type

ValueOf函数的定义如下,参数为接口类型,返回值为Value

1
2
3

var x int = 1
fmt.Println("value: ", reflect.ValueOf(x))

1
value:  <int Value>

可以通过Kind函数来检查类型,

1
2
fmt.Println("Kind:  ", reflect.ValueOf(x).Kind())
fmt.Println("Kind is Int? ", reflect.ValueOf(x).Kind() == reflect.int)

1
2
Kind:   int
Kind is Int? true

反射对象到接口值

通过Interface函数可以实现反射对象到接口值的转换,

1
func (v Value) Interface() interface {}

1
2
3
// Interface 以 interface{} 返回 v 的值
y := v.Interface().(float64)
fmt.Println(y)

修改反射对象

修改反射对象的前提条件是其值必须是可设置的

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.3) // Error: panic

为了避免这个问题,需要使用CanSet函数来检查该值的设置性,

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v: ", v.CanSet())
1
settability of v: false

那么如何才能设置该值呢?
这里需要考虑一个常见的问题,参数传递,传值还是传引用或地址?
在上面的例子中,我们使用的是reflect.ValueOf(x),这是一个值传递,传递的是x的值的一个副本,不是x本身,因此更新副本中的值是不允许的。如果使用reflect.ValueOf(&x)来替换刚才的值传递,就可以实现值的修改。

1
2
3
4
5
6
7
8
var x float64 = 3.4
p := reflect.ValueOf(&x) // 获取x的地址
fmt.Println("settability of p: ", p.CanSet())
v := p.Elem()
fmt.Println("settability of v: ", v.CanSet())
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

1
2
3
4
settability of p: false
settability of v: true
7.1
7.1

获取结构体标签

首先介绍如何遍历结构体字段内容,
假设结构体如下,

1
2
3
4
5
6
type T struct {
A int
B string
}

t := T{12, "skidoo"}

从而,通过反射来遍历所有的字段内容

1
2
3
4
5
6
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface())
}

1
2
0 A int = 23
1 B string = skidoo

接下来,如何获取结构体的标签内容?

1
2
3
4
5
6
7
8
9
10
func main() {
type S struct {
F string `species:"gopher" color:"blue"`
}

s := S{}
st := reflect.TypeOf(s)
field := st.Field(0)
fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"))
}

interface{}到函数反射

一般情况下,为了存储多个函数值,一般采用map来存储。其中key为函数名称,而value为相应的处理函数。
在这里需要定义好函数类型,但是函数的参数以及返回类型就需要是统一的,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func say(text string) {
fmt.Println(text)
}

func main() {
var funcMap = make(map[string]func(string))
funcMap["say"] = say
funcMap["say"]("hello")
}

如果希望map可以存储任意类型的函数(参数不同,返回值不同),那么就需要用interface{}而不是func(param…)来定义value。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func say(text string) {
fmt.Println(text)
}

func main() {
var funcMap = make(map[string]interface{})
funcMap["say"] = say
funcMap["say"]("hello")
}
1
cannot call non-function funcMap["say"] (type interface {})

直接调用会报错,提示不能调用interface{}类型的函数。

这时,需要利用reflect把函数从interface转换到函数来使用,

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

import (
"fmt"
"reflect"
)

func say(text string) {
fmt.Println(text)
}

func Call(m map[string]interface{}, name string, params ... interface{}) (result []reflect.Value) {
f := reflect.ValueOf(m[name]) // 把函数转化为reflect value
in := make([]reflect.Value, len(params))
for k, param := range params {
in[k] = reflect.ValueOf(param) // 把参数转为为[] reflect value
}
result = f.Call(in)
return
}

func main() {
var funcMap = make(map[string]interface{})
funcMap["say"] = say
Call(funcMap, "say", "hello")

其中,使用Call函数,调用函数v,参数为[]value

1
func (v Value) Call(in []Value) []Value