Golang 反射(Reflection)教程

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高级概念,但使用要小心。使用反射很难写出清晰易维护的代码,如果没有必要尽可能少用反射。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值