实战演⽰Go反射的使⽤⽅法和应⽤场景
今天来聊⼀个平时⽤的不多,但是很多框架或者基础库会⽤到的语⾔特性--反射,反射并不是Go语⾔独有的能⼒,其他编程语⾔都有。这篇⽂章的⽬标是简单地给⼤家梳理⼀下反射的应⽤场景和使⽤⽅法。
我们平时写代码能接触到与反射联系⽐较紧密的⼀个东西是结构体字段的标签,这个我准备放在后⾯的⽂章再梳理。
我准备通过⽤反射搞⼀个通⽤的SQL构造器的例⼦,带⼤家掌握反射这个知识点。这个是看了国外⼀个博主写的例⼦,觉得思路很好,我⼜对其进⾏了改进,让构造器的实现更丰富了些。
本⽂的思路参考⾃:/reflection/ ,本⽂内容并⾮只是对原⽂的简单翻译,具体看下⾯的内容吧~!
什么是反射
反射是程序在运⾏时检查其变量和值并找到它们类型的能⼒。听起来⽐较笼统,接下来我通过⽂章的例⼦⼀步步带你认识反射。
为什么需要反射
当学习反射的时候,每个⼈⾸先会想到的问题都是 “为什么我们要在运⾏时检查变量的类型呢,程序⾥的变量在定义的时候我们不都已经给他们指定好类型了吗?” 确实是这样的,但也并⾮总是如此,看到这你可能⼼⾥会想,⼤哥,你在说什么呢,em... 还是先写⼀个简单的程序,解释⼀下。
package main
import (
"fmt"
)
func main() {
i := 10
fmt.Printf("%d %T", i, i)
}
在上⾯的程序⾥, 变量i的类型在编译时是已知的,我们在下⼀⾏打印了它的值和类型。qq红包怎么发
现在让我们理解⼀下 ”在运⾏时知道变量的类型的必要“。假设我们要编写⼀个简单的函数,它将⼀个结构体作为参数,并使⽤这个参数创建⼀个SQL插⼊语句。
考虑⼀下下⾯这个程序
package main
import (
"fmt"
)怎样自己交社保
type order struct {
ordId int
customerId int
}
func main() {
o := order{
ordId: 1234,
customerId: 567,
}
fmt.Println(o)
}
我们需要写⼀个接收上⾯定义的结构体o作为参数,返回类似INSERT INTO order VALUES(1234, 567)这样的SQL语句。这个函数定义写来很容易,⽐如像下⾯这样。
package main
import (
"fmt"
)
type order struct {
ordId int
customerId int
}
func createQuery(o order) string {
i := fmt.Sprintf("INSERT INTO order VALUES(%d, %d)", o.ordId, o.customerId)
return i
}
func main() {
o := order{
ordId: 1234,
customerId: 567,
}
魟
fmt.Println(createQuery(o))
}
上⾯例⼦的createQuery使⽤参数o 的ordId和customerId字段创建SQL。
现在让我们将我们的SQL创建函数定义地更抽象些,下⾯还是⽤程序附带说明举⼀个案例,⽐如我们想泛化我们的SQL创建函数使其适⽤于任何结构体。
package main
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func createQuery(q interface{}) string {
}
现在我们的⽬标是,改造createQuery函数,让它能接受任何结构作为参数并基于结构字段创建INSERT 语句。⽐如如果传给createQuery的参数不再是order类型的结构体,⽽是employee类型的结构体时
e := employee {
name: "Naveen",
id: 565,
address: "Science Park Road, Singapore",
salary: 90000,
country: "Singapore",
}
那它应该返回的INSERT语句应该是
INSERT INTO employee (name, id, address, salary, country)
VALUES("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
由于createQuery 函数要适⽤于任何结构体,因此它需要⼀个 interface{}类型的参数。为了说明问题,简单起见,我们假定createQuery函数只处理包含string 和 int 类型字段的结构体。
编写这个createQuery函数的唯⼀⽅法是检查在运⾏时传递给它的参数的类型,找到它的字段,然后创建SQL。这⾥就是需要反射发挥⽤的地⽅啦。在后续步骤中,我们将学习如何使⽤Go语⾔的反射包来实现这⼀点。cad怎么缩放
Go语⾔的反射包
Go语⾔⾃带的reflect包实现了在运⾏时进⾏反射的功能,这个包可以帮助识别⼀个interface{}类型变量其底层的具体类型和值。我们
的createQuery函数接收到⼀个interface{}类型的实参后,需要根据这个实参的底层类型和值去创建并返回INSERT语句,这正是反射包的作⽤所在。
在开始编写我们的通⽤SQL⽣成器函数之前,我们需要先了解⼀下reflect包中我们会⽤到的⼏个类型和⽅法,接下来我们先逐个学习⼀下。reflect.Type 和 reflect.Value
经过反射后interface{}类型的变量的底层具体类型由reflect.Type表⽰,底层值由reflect.Value表⽰。reflect包⾥有两个函数reflect.TypeOf()
和reflect.ValueOf() 分别能将interface{}类型的变量转换为reflect.Type和reflect.Value。这两种类型是创建我们的SQL⽣成器函数的基础。
让我们写⼀个简单的例⼦来理解这两种类型。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
fmt.Println("Type ", t)
fmt.Println("Value ", v)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
保持平衡英语createQuery(o)
}
上⾯的程序会输出:
Type der
Value {456 56}
上⾯的程序⾥createQuery函数接收⼀个interface{}类型的实参,然后把实参传给了reflect.Typeof和reflect.Valueof 函数的调⽤。从输出,我们可以看到程序输出了interface{}类型实参对应的底层具体类型和值。
Go语⾔反射的三法则
这⾥插播⼀下反射的三法则,他们是:
1. 从接⼝值可以反射出反射对象。
2. 从反射对象可反射出接⼝值。
3. 要修改反射对象,其值必须可设置。
登飞来峰拼音版
反射的第⼀条法则是,我们能够吧Go中的接⼝类型变量转换成反射对象,上⾯提到的reflect.TypeOf和 reflect.ValueOf 就是完成的这种转换。第⼆条指的是我们能把反射类型的变量再转换回到接⼝类型,最后⼀条则是与反射值是否可以被更改有关。三法则详细的说明可以去看看德莱⽂⼤神写的⽂章
下⾯我们接着继续了解完成我们的SQL⽣成器需要的反射知识。
reflect.Kind
reflect包中还有⼀个⾮常重要的类型,reflect.Kind。
reflect.Kind和reflect.Type类型可能看起来很相似,从命名上也是,Kind和Type在英⽂的⼀些Phra是可以互转使⽤的,不过在反射这块它们有挺⼤区别,从下⾯的程序中可以清楚地看到。
package main
import (
"fmt"
"reflect"
)
黄巾起义时间type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
上⾯的程序会输出
Type der
Kind struct
通过输出让我们清楚了两者之间的区别。reflect.Type 表⽰接⼝的实际类型,即本例中der ⽽Kind表⽰类型的所属的种类,
即der是⼀个「struct」类型,类似的类型map[string]string的Kind就该是「map」。
反射获取结构体字段的⽅法
我们可以通过reflect.StructField类型的⽅法来获取结构体下字段的类型属性。reflect.StructField可以通过reflect.Type提供的下⾯两种⽅式拿到。
// 获取⼀个结构体内的字段数量
NumField() int
// 根据 index 获取结构体内字段的类型对象
Field(i int) StructField
// 根据字段名获取结构体内字段的类型对象
FieldByName(name string) (StructField, bool)
reflect.structField是⼀个struct类型,通过它我们⼜能在反射⾥知道字段的基本类型、Tag、是否已导出等属性。
type StructField struct {
Name string
Type Type // field type
Tag StructTag // field tag string
......
}
与reflect.Type提供的获取Field信息的⽅法相对应,reflect.Value也提供了获取Field值的⽅法。
func (v Value) Field(i int) Value {
...
}
func (v Value) FieldByName(name string) Value {
北京外国语分数线...
}
这块需要注意,不然容易迷惑。下⾯我们尝试⼀下通过反射拿到order结构体类型的字段名和值