Skip to main content
 首页 » 编程设计

Golang 反射(Reflection)教程

2022年07月19日139kerrycode

Golang反射是元编程主题,本文将尽量使其简化,让你轻松掌握并在合适场景中灵活应用。

从需求谈起

反射是一种编程能力,可以在运行时检查变量的类型和值。也许你还不理解这意思,读完本文你将会有清晰理解反射的概念。

那什么情况下需要在运行时获取变量的类型和值呢?假如有这样一个需求,写一个函数带有struct类型作为参数,函数功能是根据参数创建INSERT SQL语句保存数据。

type User struct { 
	UserId      int 
	UserName    string 
} 
 

下面我们写插入函数:

func Save(data User) string  { 
	sql := fmt.Sprintf("insert into User values(%d, '%s')", data.UserId, data.UserName) 
	return sql 
} 
 
func main() { 
	user01 := User{ 
		UserId:      1234, 
		UserName:    "Jack", 
	} 
	fmt.Println(user01) 
 
	fmt.Println(Save(user01)) 
 
} 

输出结果:

insert into User values(1234, 'Jack')

现在我们重构程序,让它更通用,可以接收任何结构体。

首先增加新的结构体Employee,并让Save函数的参数类型为interface{};

type User struct { 
	UserId      int 
	UserName    string 
} 
 
type Employee struct { 
	EmpId   int 
	EmpName string 
	Address string 
} 
 
emp01 := ref.Employee { 
    EmpId: 107, 
    EmpName: "Mary", 
    Address: "Beijing DongLu 221", 
} 
 
func Save(data interface{}) string  { 
 
} 
 

我们期望当给Save函数分别传入user01和emp01对象时,输出结果正确:

insert into User values(1234, 'Jack')

insert into Employee values(107, 'Mary', 'Beijing DongLu 221')

Save函数接收任何结构体,因此需要在运行时检查参数类型,这就需要使用反射。下面我们学习 reflect包并实现上述需求。

reflect实现通用逻辑

reflect可以在运行时识别interface{}参数底层具体类型,这正是我们需要的功能。首先需要了解reflect包提供的几个类型和方法。下面逐个讲解。

  • reflect.Type 和 reflect.Value

interface{}具体类型通过reflect.Type表示,对应值通过reflect.Value表示。 reflect.TypeOf() 和 reflect.ValueOf() 函数分别返回reflect.Type和reflect.Value,利用这两个类型我们可以实现通用逻辑。

func Save(data interface{})  { 
	t := reflect.TypeOf(data) 
	v := reflect.ValueOf(data) 
 
	fmt.Println("Type ", t) 
	fmt.Println("Value ", v) 
} 
 
func main() { 
	user01 := User{ 
		UserId:      1234, 
		UserName:    "Jack", 
	} 
 
	emp01 := Employee { 
		EmpId: 107, 
		EmpName: "Mary", 
		Address: "Beijing DongLu 221", 
	} 
 
	ref.Save(user01) 
	ref.Save(emp01) 
} 

输出结果:

Type  main.User 
Value {1234 Jack} 
Type  main.Employee 
Value {107 Mary Beijing DongLu 221} 

通过输出可以看到参数的具体类型及其值。

  • reflect.Kind

reflect包中还有一种类型Kind,与Type类似,但有区别。下面通过程序来解释:

type User struct { 
	UserId      int 
	UserName    string 
} 
 
func Save(data interface{})  { 
    t := reflect.TypeOf(data) 
    k := t.Kind() 
    fmt.Println("Type ", t) 
    fmt.Println("Kind ", k) 
} 
 
func main() {   
	user01 := User{ 
		UserId:      1234, 
		UserName:    "Jack", 
	} 
    Save(user01) 
 
} 
 

输出结果:

Type  main.User   
Kind  struct   

现在应该理解两者差异了吧,Type表示interface{}实际业务类型:main.User ,Kind表示变量类型:Struct。

  • NumField() 和 Field() 方法

NumField()方法返回结构体属性数量,Field(i int) 返回第i个属性的值。

type User struct { 
	UserId      int 
	UserName    string 
} 
 
func Save(data interface{})  { 
    if reflect.TypeOf(q).Kind() == reflect.Struct { 
        v := reflect.ValueOf(q) 
        fmt.Println("Number of fields", v.NumField()) 
        for i := 0; i < v.NumField(); i++ { 
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i)) 
        } 
    } 
} 
 
func main() {   
	user01 := User{ 
		UserId:      1234, 
		UserName:    "Jack", 
	} 
    Save(user01) 
} 
 

输出结果:

Number of fields 2 
Field:0 type:reflect.Value value:1234 
Field:1 type:reflect.Value value:Jack 

完整程序如下:

import ( 
	"fmt" 
	"reflect" 
) 
 
type User struct { 
	UserId      int 
	UserName    string 
} 
 
type Employee struct { 
	EmpId   int 
	EmpName string 
	Address string 
} 
 
 
func Save(data interface{})  { 
	if reflect.TypeOf(data).Kind() == reflect.Struct { 
		t := reflect.TypeOf(data).Name() 
		query := fmt.Sprintf("insert into %s values(", t) 
 
		v := reflect.ValueOf(data) 
		for i := 0; i < v.NumField(); i++ { 
			switch v.Field(i).Kind() { 
			case reflect.Int: 
				if i == 0 { 
					query = fmt.Sprintf("%s%d", query, v.Field(i).Int()) 
				} else { 
					query = fmt.Sprintf("%s, %d", query, v.Field(i).Int()) 
				} 
			case reflect.String: 
				if i == 0 { 
					query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String()) 
				} else { 
					query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String()) 
				} 
			default: 
				fmt.Println("Unsupported type") 
				return 
			} 
		} 
		query = fmt.Sprintf("%s)", query) 
		fmt.Println(query) 
		return 
 
	} 
	fmt.Println("unsupported type") 
} 
 
 
func main() { 
	user01 := User{ 
		UserId:      1234, 
		UserName:    "Jack", 
	} 
 
	emp01 := Employee { 
		EmpId: 107, 
		EmpName: "Mary", 
		Address: "Beijing DongLu 221", 
	} 
 
	Save(user01) 
	Save(emp01) 
} 
 

输出如下:

insert into User values(1234, "Jack") 
insert into Employee values(107, "Mary", "Beijing DongLu 221") 

上面代码首先检查参数变量类型,如果是结构体,再通过 reflect.TypeOf(data).Name() 获得结构体名称作为表名。下面获得参数的值,并通过循环获得每个属性的值,switch语句判断数据类型,如果是字符串类型则加上引号包裹变量值。

通过上面示例我们了解了反射的能力,在实现通用业务时可以让程序更简洁。反射包中还有其他一些函数也非常有用,下面介绍几个常用的函数,并给出示例。

反射包其他方法

  • reflect.Copy()

从源拷贝数据至目标,返回拷贝元素数量。源和目标必须是相同类型,如:Slice 或 数组,另外数据元素的类型也相同:

import ( 
	"fmt" 
	"reflect" 
) 
 
func main() { 
	destination := reflect.ValueOf([]string{"A", "B", "C"}) 
	source := reflect.ValueOf([]string{"D", "E", "F"}) 
 
	// Copy() function is used and it returns the number of elements copied 
	counter := reflect.Copy(destination, source) 
	fmt.Println(counter) 
 
	fmt.Println(source) 
	fmt.Println(destination) 
} 

输出结果:

3
[D E F]
[D E F]

  • reflect.DeepEqual()

DeepEqual返回X 和 Y 是否深度相等。数组则对应每个元素要相等,结构体则每个属性(无论公开或私有)要相等,Slice需要它们都是nil或非nil,它们有相同的长度,或者它们指向相同基础数组的相同初始项。

import ( 
	"fmt" 
	"reflect" 
) 
 
type mobile struct { 
	price float64 
	color string 
} 
 
func main() { 
	// DeepEqual is used to check two slices are equal or not 
	s1 := []string{"A", "B", "C", "D", "E"} 
	s2 := []string{"D", "E", "F"} 
	result := reflect.DeepEqual(s1, s2) 
	fmt.Println(result) 
 
	// DeepEqual is used to check two arrays are equal or not 
	n1 := [5]int{1, 2, 3, 4, 5} 
	n2 := [5]int{1, 2, 3, 4, 5} 
	result = reflect.DeepEqual(n1, n2) 
	fmt.Println(result) 
 
	// DeepEqual is used to check two structures are equal or not 
	m1 := mobile{500.50, "red"} 
	m2 := mobile{400.50, "black"} 
	result = reflect.DeepEqual(m1, m2) 
	fmt.Println(result) 
} 

输出结果:

false
true
false

  • reflect.Swapper()

Swapper函数用于交换slice中两个位置的元素,还可以利用这个功能实现slice排序。

import ( 
	"fmt" 
	"reflect" 
) 
 
func main() { 
	theList := []int{1, 2, 3, 4, 5} 
	swap := reflect.Swapper(theList) 
	fmt.Printf("Original Slice :%v\n", theList) 
 
	// Swapper() function is used to swaps the elements of slice 
	swap(1, 3) 
	fmt.Printf("After Swap :%v\n", theList) 
 
	// Reversing a slice using Swapper() function 
	for i := 0; i < len(theList)/2; i++ { 
		swap(i, len(theList)-1-i) 
	} 
	fmt.Printf("After Reverse :%v\n", theList) 
} 

输出结果:

Original Slice :[1 2 3 4 5]
After Swap :[1 4 3 2 5]
After Reverse :[5 2 3 4 1]

  • reflect.MakeSlice()

MakeSlice创建新slice,并初始化零值。可以指定类型、长度及容量。

import ( 
	"fmt" 
	"reflect" 
) 
 
func main() { 
	var str []string 
	var strType reflect.Value = reflect.ValueOf(&str) 
	newSlice := reflect.MakeSlice(reflect.Indirect(strType).Type(), 10, 15) 
 
	fmt.Println("Kind :", newSlice.Kind()) 
	fmt.Println("Length :", newSlice.Len()) 
	fmt.Println("Capacity :", newSlice.Cap()) 
} 

输出:

Kind : slice
Length : 10
Capacity : 15

  • reflect.MakeMap()

创建指定类型的map变量。

import ( 
	"fmt" 
	"reflect" 
) 
 
func main() { 
	var str map[string]string 
	var strType reflect.Value = reflect.ValueOf(&str) 
	newMap := reflect.MakeMap(reflect.Indirect(strType).Type()) 
 
	fmt.Println("Kind :", newMap.Kind()) 
} 

map

-reflect.MakeChan()

动态创建channel,指定类型与缓存大小。

import ( 
	"fmt" 
	"reflect" 
) 
 
func main() { 
	var str chan string 
	var strType reflect.Value = reflect.ValueOf(&str) 
	newChannel := reflect.MakeChan(reflect.Indirect(strType).Type(), 512) 
 
	fmt.Println("Kind :", newChannel.Kind()) 
	fmt.Println("Capacity :", newChannel.Cap()) 
} 

返回结果:

Kind : chan
Capacity : 512

总结

反射对开发者来说是极其强大的工具,可以在运行时检查、修改、创建变量以及结构体。Type, Kind 和 Value是反射中三个重要概念,利用它们可以在运行时获取变量的具体类型和值。

本文通过示例展示了反射实际应用场景及一些常用函数的应用方式。现在我们考虑什么场景使用反射这个问题。这里直接引用 Rob Pike 关于这个话题的回答:

Clear is better than clever. Reflection is never clear.
反射很强大,是Golang高级概念,但使用要小心。使用反射很难写出清晰易维护的代码,如果没有必要尽可能少用反射。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/122813580
阅读延展