目录

Reflection 反射

概述

Go是一门具有良好反射支持的静态语言。Go中提供的反射功能带来了很多动态特性。这种机制可以让我们在程序运行的时候动态地检查和修改对象的类型和值,比如说它可以实现以下的一些功能:

  1. 动态创建对象
  2. 动态调用函数
  3. 动态修改对象
  4. 动态获取对象信息,如类型、值、字段等信息
  5. 动态序列化和反序列化

值得注意的是的,反射无法获取变量的变量名。

基本概念

开始使用reflect之前,我们需要了解reflect包里面的两个基本类型:

  • reflect.Type 是Go中的类型(type)的表示
  • reflect.Value 是Go中对值(value)的反射接口

并且,相对应的,还有两个函数来获取这两个基本类型:

  • reflect.TypeOf(i any) Type
  • reflect.ValueOf(i any) Value
示例:使用TypeOf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
    "fmt"
    "reflect"
)

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

会输出:

1
type: float64
示例:使用ValueOf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 3.14

	fmt.Println("x value: ", reflect.ValueOf(x))

	// 调用一些常用的方法
	fmt.Println("x.type", reflect.ValueOf(x).Type())
	fmt.Println("x.kind == reflect.Float64: ", reflect.ValueOf(x).Kind() == reflect.Float64) // 返回x的underlying type
	fmt.Println("value of x: ", reflect.ValueOf(x).Float())
}

程序运行结果如下:

1
2
3
4
x value:  3.14
x.type float64
x.kind == reflect.Float64:  true
value of x:  3.14

反射的三条定律

The Laws of Reflection一文中介绍到了Go语言中使用到反射的三条定律,理解这三条定律可以让我们更好的使用反射相关的内容。

定律1

Reflection goes from interface value to reflection object.

在基本层面上,反射只是一种检查存储在接口(interface)变量中的类型(type)和值(value)的机制。

举个例子来说,我们下面调用ValueOf(x)

1
2
x := 3.14
reflect.ValueOf(x)

实质上是先将x存储在一个空的interface{}中,然后再当作参数传递给reflect.ValueOf,之后reflect.ValueOf再从interface{}中恢复类型信息。

定律2

Reflection goes from reflection object to interface value.

也就是说给定一个reflect.Value,我们可以使用Interface方法恢复一个接口值(interface value);实际上,该方法将类型和值信息打包回接口并返回结果:

1
2
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

定律3

To modify a reflection object, the value must be settable.

示例:不可修改
1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

这种是因为x不能修改,若直接去修改x的值,则会报错。

我们可以通过CanSet方法去判断Value是否可以被修改。

示例:将不可修改的示例改成可修改

实质上是通过指针去进行操作。

1
2
3
4
5
6
7
8
9
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
v := p.Elem()  // To get to what p points to, we call the Elem method of Value, which indirects through the pointer
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

程序运行结果如下:

1
2
3
4
5
type of p: *float64
settability of p: false
settability of v: true
7.1
7.1

为什么需要反射

其实反射某种程度就是元编程(meta programming),在部分情况通过常规编程很难做到的场景,我们需要通过反射来快速的达到我们的目的。

举个例子来说,假设现在我有一个生产环境的数据表定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// GORM定义的数据表
type RedisBase struct {
	Time   time.Time `gorm:"unique"`
	Ip1In  int
	Ip1Out int
	Ip2In  int
	Ip2Out int
	Ip3In  int
	Ip3Out int
	Ip4In  int
	Ip4Out int
	Ip5In  int
	Ip5Out int
	Ip6In  int
	Ip6Out int
}

是的,这个表看上去就是那么的不合理,但是历史环境造成的问题,很难去通过改数据表来进行,但是若此时我们的数据格式是:

1
2
3
// 若存在数据,则第一个对应ip1以此类推
var inData = []int{}
var outData = []int{}

如果我们这里不能使用反射,则需要用下面的方式写数据表:

1
2
3
4
5
6
7
r = RedisBase{
	Ip1In inData[0]
	Ip1Out outData[0]
	Ip1In inData[1]
	Ip1Out outData[1]
	...
}

显得异常的繁琐,而我们这要是通过反射的方式,可以变得更加灵活:

 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"
)

// GORM定义的数据表
type RedisBase struct {
	Ip1In  int
	Ip1Out int
	Ip2In  int
	Ip2Out int
	Ip3In  int
	Ip3Out int
	Ip4In  int
	Ip4Out int
	Ip5In  int
	Ip5Out int
	Ip6In  int
	Ip6Out int
}

func main() {
	var inData = []int{1, 2, 3, 4, 5, 6}
	var outData = []int{6, 5, 4, 3, 2, 1}

	r := RedisBase{}

	v := reflect.ValueOf(&r).Elem()
	for i := 0; i < len(inData); i++ {
		v.FieldByName(fmt.Sprintf("Ip%dIn", i+1)).SetInt(int64(inData[i]))
		v.FieldByName(fmt.Sprintf("Ip%dOut", i+1)).SetInt(int64(outData[i]))
	}

	fmt.Println(r) // {1 6 2 5 3 4 4 3 5 2 6 1}
}

这样写代码就会简单很多,可以写出更优雅、可复用的代码。

使用

struct相关

reflect可以与struct进行交互,主要是访问struct以及修改struct里面的字段。

读取struct

示例:通过反射读取struct的值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"fmt"
	"reflect"
)

type T struct {
	A int
	B string
}

func main() {
	t := T{23, "skidoo"}
	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

修改struct的值

修改的struct的字段需要是大写的字段名,否则是无法被访问的。

示例:通过反射修改struct的值
 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
package main

import (
	"fmt"
	"reflect"
)

type T struct {
	A int
	B string
}

func main() {
	t := T{23, "skidoo"}
	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())
	}

	// 修改字段的值
	s.Field(0).SetInt(77)
	s.FieldByName("B").SetString("Sunset Strip")
	fmt.Println("t is now: ", t)
}

运行结果如下所示:

1
2
3
0: A int = 23
1: B string = skidoo
t is now:  {77 Sunset Strip}

获取标签值

我们可以通过反射来检视结构体字段的标签信息。 结构体字段标签的类型为reflect.StructTag,它的方法GetLookup用来检视字段标签中的键值对。

示例:获取struct的标签值
 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"
)

type T struct {
	A int `max:"99" min:"0" defalut:"0"`
	B string `optional:"yes"`
}

func main() {
	t := reflect.TypeOf(T{})

	x:= t.Field(0).Tag
	y:=t.Field(1).Tag

	fmt.Println("Tag type: ", reflect.TypeOf(x))
	// v的类型为string
	v, ok := x.Lookup("max")
	fmt.Println(len(v), ok) // 2 true
	fmt.Println(x.Lookup("optional")) //  false
	fmt.Println(y.Lookup("optional")) // yes true
}

程序输出内容:

1
2
3
4
Tag type:  reflect.StructTag
2 true
 false
yes true

slice相关

当类型为slice时,我们可以用Len方法获取长度,并且通过Index方法来获取对应的值。

示例:遍历interface
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func test2(value interface{}){

	switch reflect.TypeOf(value).Kind(){
	case reflect.Slice:
		r := reflect.ValueOf(value)
		for i:=0; i<r.Len(); i++{
			log.Println(r.Index(i).Interface())
		}
	default:
		log.Fatal("类型不为slice")
	}
}

func main(){
    test2([]string{"a", "b"})

	test2(1)
}

参考文献

  1. The Laws of Reflection
  2. Go 101 反射
  3. reflect包官方文档