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') 
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
 
Kind 
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) 
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() 
通过上面示例我们了解了反射的能力,在实现通用业务时可以让程序更简洁。反射包中还有其他一些函数也非常有用,下面介绍几个常用的函数,并给出示例。
反射包其他方法
- 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高级概念,但使用要小心。使用反射很难写出清晰易维护的代码,如果没有必要尽可能少用反射。