反射是Go中的一个高级话题。我将尽可能地使它简单化。
本教程有以下几个部分。
什么是反射?
有什么必要检查一个变量并找到它的类型?
反射包
reflect.Type和reflect.Value
reflect.Kind
NumField()和Field()方法
Int()和String()方法
完整的程序
应该使用反射吗?
现在让我们逐一讨论这些部分。
什么是反射? 反射是指程序在运行时检查其变量和数值并找到其类型的能力。你可能不明白这意味着什么,但这没有关系。在本教程结束时,你会对反射有一个清晰的认识,所以请跟着我。
为什么要检查一个变量并找到其类型呢? 任何人在学习反射时都会遇到的第一个问题是,既然我们程序中的每一个变量都是由我们自己定义的,而且我们在编译时就知道它的类型,为什么还要在运行时检查变量并找到它的类型?嗯,大多数时候是这样的,但并不总是这样。
让我解释一下我的意思。让我们写一个简单的程序。
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { i := 10 fmt.Printf("%d %T" , i, i) }
在操场上运行
在上面的程序中,i的类型在编译时就已经知道了,我们在下一行打印它。这里没有什么神奇之处。
现在我们来了解一下在运行时知道变量类型的必要性。假设我们想写一个简单的函数,该函数将接受一个结构体作为参数,并使用它创建一个SQL插入查询。
请考虑以下程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" ) type order struct { ordId int customerId int } func main () { o := order{ ordId: 1234 , customerId: 567 , } fmt.Println(o) }
在操场上跑步
我们需要写一个函数,将上述程序中的结构o作为参数,并返回以下SQL插入查询。
1 insert into order values(1234, 567)
这个函数的编写很简单。我们现在就来做这件事。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "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)) }
在操场上运行
第12行的createQuery函数通过使用o的ordId和customerId字段创建插入查询,这个程序将输出:
1 insert into order values(1234, 567)
现在让我们把我们的查询创建器提高到一个新的水平。如果我们想使我们的查询创建器通用化,并使其适用于任何结构,该怎么办。让我解释一下我用程序的意思。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package maintype order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery (q interface {}) string { } func main () {}
我们的目标是完成上述程序第16行的createQuery函数,使其接受任何结构体作为参数,并根据结构体字段创建一个插入查询。
例如,如果我们传递下面的结构。
1 2 3 4 o := order { ordId: 1234 , customerId: 567 }
我们的createQuery函数应该返回。
1 insert into order values (1234, 567)
同样地,如果我们通过
1 2 3 4 5 6 7 e := employee { name: "Naveen" , id: 565 , address: "Science Park Road, Singapore" , salary: 90000 , country: "Singapore" , }
它应该返回。
1 insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
由于createQuery函数应适用于任何结构,它需要一个interface{}作为参数。为了简单起见,我们将只处理包含string和int类型字段的结构,但这可以扩展到任何类型。
createQuery函数应适用于任何结构。编写这个函数的唯一方法是在运行时检查传递给它的结构参数的类型,找到其字段,然后创建查询。这就是反射的用处。在本教程的下一步,我们将学习如何使用反射包实现这一目标。
反射包 反射包 实现了Go中的运行时反射。反射包有助于识别底层的具体类型和interface{}变量的值。这正是我们所需要的。createQuery函数需要一个interface{}参数,查询需要根据interface{}参数的具体类型和值来创建。这正是反射包所帮助完成的。
在编写我们的通用查询生成器程序之前,我们需要首先了解反射包中的一些类型和方法。让我们一个一个地看一下它们。
reflect.Type和reflect.Value interface{}的具体类型由 reflect.Type 表示,底层值由 reflect.Value 表示。有两个函数 reflect.TypeOf() 和 reflect.ValueOf() 分别返回 reflect.Type 和 reflect.Value。这两个类型是创建我们的查询生成器的基础。让我们写一个简单的例子来理解这两种类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "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) }
在操场上运行
在上面的程序中,第13行的createQuery函数以interface{}为参数。13行的createQuery函数需要一个interface{}作为参数。第14行的函数reflect.TypeOf以interface{}为参数,并返回包含所传递的interface{}参数的具体类型的reflect.Type。同样地,第15行的reflect.ValueOf函数以interface{}为参数,并返回reflect.Value,其中包含所传递的interface{}参数的基本值。
上述程序打印出。
1 2 Type main.order Value {456 56}
从输出中,我们可以看到,程序打印了接口的具体类型和值。
reflect.Kind 在反射包中还有一个重要的类型叫Kind 。
反射包中的类型Kind和Type看起来很相似,但它们有一个区别,从下面的程序中就可以看出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "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) }
在操场上运行
上面的程序输出。
1 2 Type main.order Kind struct
我想你现在会清楚这两者之间的区别了。Type表示interface{}的实际类型,在本例中是main.Order,Kind表示该类型的具体种类。在本例中,它是一个struct。
Kind既可以为reflect.TypeOfKind也可以为reflect.ValueOf.Kind
NumField()和Field()方法 NumField()方法返回结构体中字段的数量,Field(i int)方法返回第i个字段的reflect.Value。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery (q interface {}) { if reflect.ValueOf(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 () { o := order{ ordId: 456 , customerId: 56 , } createQuery(o) }
在操场上运行
在上面的程序中,在第14行,我们首先检查q的类型是否为struct,因为NumField方法只对结构体起作用。程序的其余部分是不言自明的。这个程序的输出。
1 2 3 Number of fields 2 Field:0 type:reflect.Value value:456 Field:1 type:reflect.Value value:56
Int()和String()方法 Int和String方法分别帮助将reflect.Value提取为int64和string。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) func main () { a := 56 x := reflect.ValueOf(a).Int() fmt.Printf("type:%T value:%v\n" , x, x) b := "Naveen" y := reflect.ValueOf(b).String() fmt.Printf("type:%T value:%v\n" , y, y) }
在操场上运行
在上面的程序中,在第10行,我们将reflect.Value提取为int64,在第13行,我们将其提取为string,这个程序打印:
1 2 type:int64 value:56 type:string value:Naveen
完成程序 现在我们有足够的知识来完成我们的查询生成器,让我们继续做吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package mainimport ( "fmt" "reflect" ) type order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery (q interface {}) { if reflect.ValueOf(q).Kind() == reflect.Struct { t := reflect.TypeOf(q).Name() query := fmt.Sprintf("insert into %s values(" , t) v := reflect.ValueOf(q) 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 () { o := order{ ordId: 456 , customerId: 56 , } createQuery(o) e := employee{ name: "Naveen" , id: 565 , address: "Coimbatore" , salary: 90000 , country: "India" , } createQuery(e) i := 90 createQuery(i) }
在操场上运行
在第22行,我们首先检查传递的参数是否是一个struct,第28行的case语句检查当前字段是否为reflect.Int,如果是,我们使用Int()方法提取该字段的值为int64。if else语句是用来处理边缘情况的。请添加日志(logs)以了解为什么需要它。类似的逻辑被用于提取第34行的string。
我们还添加了检查,以防止程序在不支持的类型被传递给createQuery函数时崩溃。该程序的其余部分是不言自明的。我建议在适当的地方添加日志,并检查其输出,以更好地理解这个程序。
这个程序可以打印出来。
1 2 3 insert into order values(456, 56) insert into employee values("Naveen", 565, "Coimbatore", 90000, "India") unsupported type
我想让你们把字段名添加到输出的查询中,作为一个练习。请尝试改变程序以打印格式的查询。
1 insert into order(ordId, customerId) values(456, 56)
应该使用反射吗? 在展示了反射的实际用途之后,现在是真正的问题。你应该使用反思吗?我想引用Rob Pike关于使用反射的谚语来回答这个问题。
lear is better than clever. Reflection is never clear.
反射在Go中是一个非常强大和高级的概念,应该谨慎使用。使用反射编写清晰和可维护的代码是非常困难的。应该尽可能避免使用它,只有在绝对必要时才使用。
至此,本教程结束。希望你喜欢它。祝你有一个愉快的一天。