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