Featured image of post go中的reflect

go中的reflect

go中的reflect包学习记录

什么是反射

反射指程序在运行时(runtime)可以访问,检测和修改自身状态或行为的能力。 反射的本质是在运行时候动态的获取一个变量的类型信息和值信息。 Golang 中反射可以实现以下功能:

  1. 反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
  2. 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的 方法、结构体的 tag。
  3. 通过反射,可以修改变量的值,可以调用关联的方法

在 GoLang 的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。 在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为 由 reflect.Type 和 reflect.Value 两 部 分 组 成 , 并 且 reflect 包 提 供 了 reflect.TypeOf 和 reflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type。

reflect.TypeOf()获取任意值的类型对象

1
func TypeOf(i interface{}) Type

从方法可以看出接受参数是空接口类型,可以获得任意值的类 型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

func main() {
	var a int = 32
	var b float64 = 3.14
	// reflect.TypeOf() 接受一个空接口类型,返回一个reflect.Type 类型
	typeA := reflect.TypeOf(a)
	fmt.Println(typeA)
	typeB := reflect.TypeOf(b)
	fmt.Println(typeB)
}

输出:

int
float64

type Name 和 type Kind 方法

使用reflect.Typeof()返回的是一个reflect.Type接口 在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在 Go 语言中我们可 以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中, 当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定 义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

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

type Student struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func reflectType(x interface{}) {
	typ := reflect.TypeOf(x)
	fmt.Printf("类型是:%v ,名字是 %#v 底层种类是:%#v\n", typ, typ.Name(), typ.Kind().String())
}
func main() {
	var s Student
	var a myInt
	reflectType(s)  //类型是:main.Student ,名字是 "Student" 底层种类是:"struct"
	reflectType(a)  //类型是:main.myInt ,名字是 "myInt" 底层种类是:"int"
	reflectType(&s) //类型是:*main.Student ,名字是 "" 底层种类是:"ptr"
	reflectType(&a) //类型是:*main.myInt ,名字是 "" 底层种类是:"ptr"

}

//数组,map,切片,指针的Name()返回是空

在reflect包中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
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
)

reflect.ValueOf()函数

reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原 始值之间可以互相转换。 reflect.Value 类型提供的获取原始值的方法如下: Alt text 如何将一个原始值转为Value类型?

1
value := refect.ValueOf(10)  //将int类型的10转为Value类型

例子:refelct.Value和原始值的转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
	var a int64 = 33
	var b float64 = 3.14
	reflectValue(a)
	reflectValue(b)
	v := reflect.ValueOf(10)
	f := reflect.ValueOf(3.14)
	fmt.Println(v.Kind()) //int
	fmt.Println(f.Kind()) //float64
}
func reflectValue(x interface{}) {
	val := reflect.ValueOf(x) //ValueOf返回一个初始化为接口i中存储的具体值的Value结构体。ValueOf(nil)返回零值。
	// val是reflect.Value类型不能直接和具体类型进行运算,要使用Kind()方法判断
	kind := val.Kind() // 获取值的类型
	switch kind {
	case reflect.Int64:
		fmt.Println(val.Int() + 6)
	case reflect.Float64:
		fmt.Println(val.Float() + 5.3)
	}

}

通过反射修改变量的值

注意点:要想通过反射修改变量的值,传递的必须是变量的地址,再利用Elem()方法获取变量进行设置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	var a = 3.14
	reflectSetValue(&a)
	fmt.Println(a)
}

func reflectSetValue(x interface{}) {
	val := reflect.ValueOf(x)
	switch val.Elem().Kind() {
	case reflect.Int:
		val.Elem().SetInt(50)
	case reflect.Float64:
		val.Elem().SetFloat(6.28)
	}
}

结构体反射

结构体反射应用的比较多,比如读取一个文件的配置信息然后赋值给结构体。

与结构体相关的方法

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

返回的refelct.StructField定义如下

1
2
3
4
5
6
7
8
9
type StructField struct {
    Name      string  // 字段的名称 
    PkgPath   string  // PkgPath 非导出字段的包路径,对导出字段为""
    Type      Type    // 字段的类型
    Tag       StructTag  // 字段的Tag标签
    Offset    uintptr
    Index     []int
    Anonymous bool    // 是否是匿名字段
}

相关方法如下: 结构体相关方法 修改结构体数据例子

 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

func main() {
	var s = Student{}

	reflectStruct(&s)
	fmt.Println(s)
}
func reflectStruct(x interface{}) error {
	structTyp := reflect.TypeOf(x)
	v := reflect.ValueOf(x)
	//	判断类型是否是结构体
	if structTyp.Kind() != reflect.Struct && structTyp.Elem().Kind() != reflect.Struct {
		return errors.New("expect struct ")
	}

	//修改结构体数据
	//structField, _ := structTyp.Elem().FieldByName("Name")
	//tag := structField.Tag.Get("info")

	name := v.Elem().FieldByName("Name")
	name.SetString("王五")
	age := v.Elem().FieldByName("Age")
	age.SetInt(23)
	return nil
}

例子:从文件info.txt读取内容,给结构体赋值 info.txt内容为

1
2
3
name=小明
age=22
gender=男
 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main

import (
	"errors"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
)

type Student struct {
	Name string `info:"name"`
	Age  int    `info:"age"`
	Sex  string `info:"gender"`
}

func main() {
	var s Student
	file, err := os.ReadFile("./info.txt")
	if err != nil {
		fmt.Println(err)
	}

	//fmt.Println(string(file))

	LoadingFile(string(file), &s)
	fmt.Printf("%#v", s)
}

func LoadingFile(s string, x interface{}) error {
	tpy := reflect.TypeOf(x)
	val := reflect.ValueOf(x)
	// 第一步判断必须是指针传入
	if tpy.Kind() != reflect.Ptr {
		fmt.Println("except struct ptr")
		return errors.New("except struct ptr")
	}
	//	第二部判断是否是结构体

	if tpy.Elem().Kind() != reflect.Struct {
		fmt.Println("except struct ")
		return errors.New("except struct")
	}
	splitStr := strings.Split(s, "\n")
	for _, v := range splitStr {
		kvList := strings.Split(v, "=")
		if len(kvList) != 2 {
			continue
		}
		fieldName := ""
		key := strings.TrimSpace(kvList[0])
		value := strings.TrimSpace(kvList[1])
		//	遍历结构体字段
		for i := 0; i < tpy.Elem().NumField(); i++ {
			f := tpy.Elem().Field(i)
			tagVal := f.Tag.Get("info")
			if tagVal == key {
				fieldName = f.Name
				break
			}
		}
		if fieldName == "" {
			continue
		}
		//根据找到的结构体字段名找到结构体字段
		vField := val.Elem().FieldByName(fieldName)
		switch vField.Type().Kind() {
		case reflect.String:
			vField.SetString(value)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			intVal, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return err
			}
			vField.SetInt(intVal)
		default:
			return fmt.Errorf("unsupport value type:%v", vField.Type().Kind())
		}
	}
	return nil
}

refelct反射功能比较强大,但也不能滥用,因为代码可读性比较差。而且性能比较差,反射的类型错误在程序运行的时候才会panic。