Go反射

Go反射

一、什么是反射

在编译时不知道类型的情况下,更新变量;在运行时查看未知类型变量的值、调用方法以及直接对他们的布局进行操作,这种机制称为反射。

二、为什么需要反射

当我们无法透视一个未知类型布局的时候,需要使用反射。

假设现在需要实现一个Sprint函数,将不同类型的值以字符串形式返回。 使用类型分支来实现,就需要去case每一种类型。我们不可能去穷举各种类型,更何况还可能是自定义的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func Sprint(x interface{}) string {
type stringer interface {
String() string
}

switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
case bool:
if x {
return "true"
}
return "false"
default:
return "???"
}
}

三、reflect.Type和reflect.Value

3.1.Type和Value

反射功能由reflect包实现,其定义了两个重要的类型:Type和Value

  • reflect.Type是一个有很多方法的接口,其只有一个实现 rtype (结构体)
1
2
3
4
5
6
7
8
9
10
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool) // 获取当前类型对应方法的引用
NumMethod() int
...
Implements(u Type) bool // 可以判断当前类型是否实现了某个接口
...
}
  • reflect.Value 是一个结构体,没有对外暴露的字段(都是小写的变量),不过其提供了写入和获取的方法
1
2
3
4
5
6
7
8
9
10
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}

func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte

3.2.TypeOf和ValueOf

与Type和Value相对应的,有两个重要的方法:

  • reflect.TypeOf :可以获取类型信息
  • reflect.ValueOf :可以获取数据的运行时表示
1
2
3
4
5
6
7
8
9
func main() {
t := reflect.TypeOf(3)
v := reflect.ValueOf(3)
fmt.Printf("%T, %v \n", t, t) // *reflect.rtype, int
fmt.Printf("%T, %v \n", v, v) // reflect.Value, 3

// 实际上,Prinf 的 %T 格式数据,内部就是使用了reflect.TypeOf
fmt.Printf("%s \n", reflect.TypeOf(t)) // *reflect.rtype
}

reflect.TypeOf 返回的是具体的类型而不是接口类型,例如

1
2
3
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // *os.File 而不是 io.Writer

3.3.Type Name和Type Kind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"reflect"
)

type MyInt int64

func main() {

mi := reflect.TypeOf(MyInt(1))
fmt.Printf("%v, %v\n", mi.Name(), mi.Kind()) // MyInt int64

var i = int64(1)
pi := reflect.TypeOf(&i)
fmt.Printf("%v, %v\n", pi.Name(), pi.Kind()) // , ptr
}
  • Name()返回的是在包中定义的类型名称,包括基本类型和使用type定义的自定义类型。像数组、切片、Map、指针等类型的变量,Name()返回值为空字符串
  • Kind() 返回的是底层类型(例如基本类型、指针类型、结构体类型),定义的底层类型如下:
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
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 // 底层指针
)

四、使用reflect.Value设置值

4.1.可寻址判定

一个变量是可寻址的存储区域,其中包含了一个值,并且这个值可以通过这个地址来更新。

先看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"reflect"
)

func main() {
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()

fmt.Println(a, a.CanAddr()) // 2 false
fmt.Println(b, b.CanAddr()) // 2 false
fmt.Println(c, c.CanAddr()) // 0xc000018098 false
fmt.Println(d, d.CanAddr()) // 2 true
}

因为a、b、c中ValueOf的参数,都是值传递,也就是传递的是一个拷贝的副本,因此都是不可寻址的(通过CanAddr() 方法可以判定)

而d是通过对c的指针提取得到的,因此可以寻址。

reflect.ValueOf(&x).Elem()用来获取变量x可寻址(意味着可以改变值)的Value值

4.2.设置值

有三种方法可以设置值

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

package main

import (
"fmt"
"reflect"
)

func main() {
x := 2
d := reflect.ValueOf(&x).Elem()


// 第一种:先获取地址,然后在这个地址上调用Interface()
// 这时候会返回一个包含这个指针的 interface{}值
// 最后,如果我们只打变量的类型,通过类型断言把接口内容转化为一个普通的指针,就可以更新了
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3

// 第二种:使用Set
// Set会检查是否可以赋值
// 如果类型不匹配,会panic,例如
// d.Set(reflect.ValueOf(int64(4)))
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4

// 第三种:使用Set的变种
// 还有例如SetString、SetFloat等等
d.SetInt(5)
fmt.Println(x) // 5

d.SetString("Test") // panic,因为类型不符合
}

对于interface{}空接口,可以使用Set,但是不能使用其变种

1
2
3
4
5
6
7
8
9
10
var y interface{}

ry := reflect.ValueOf(&y).Elem()

ry.Set(reflect.ValueOf(2)) // ok y=int(2)
ry.Set(reflect.ValueOf("hello2")) // ok y="hello2"
ry.SetInt(3) // panic
ry.SetString("hello3") // panic


判断一个reflect.Value是否可以被修改,应该使用CanSet()方法,CanAddr()不一定能保证。

1
2
3
fmt.Println(ry.CanAddr())  // true

fmt.Println(ry.CanSet()) // true

五、结构体反射

通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象reflect.TypeNumField()Field()方法获得结构体成员的详细信息:

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

import (
"fmt"
"reflect"
)

type student struct {
Name string `json:"name"`
Score int `json:"score"`
}

func main() {
stu1 := student{
Name: "stu1",
Score: 100,
}

t := reflect.TypeOf(stu1)
fmt.Println(t.Name(), t.Kind()) // student struct

// 遍历每个结构体字段,获取相关信息
// name:Name index:[0] type:string json tag:name
// name:Score index:[1] type:int json tag:score
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}

// 通过字段名获取指定结构体字段信息
// name:Score index:[1] type:int json tag:score
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}