反射是Go的高级用法之一,是程序在运行时检查其变量和值并找到他们的类型的能力
为什么需要反射
任何人在学习反射时都会遇到的第一个问题是,当我们程序中的每个变量都由我们定义并且我们在编译时本身就知道它的类型时,为什么我们甚至需要在运行时检查一个变量并找到它的类型。嗯,这在大多数情况下都是正确的,但并非总是如此。
举一个栗子来引入反射的作用
假设我们要编写一个简单的函数,它将一个结构作为参数,并使用它创建一个 SQL 插入查询:
我们需要编写一个函数,将上面程序中的结构o作为参数,并返回以下 SQL 插入查询:
insert into order values(1234, 567)
函数如下:
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插入语句。该程序将输出:
insert into order values(1234, 567)
现在我们来升级这个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 {
}
func main() {
}
由于该createQuery函数应该适用于任何结构,因此它需要一个interface{}作为参数。为简单起见,我们只处理包含 string 和 int 类型字段的结构体,但可以扩展为包含任何类型的字段。
createQuery 函数应该适用于所有的结构体。编写此函数的唯一方法是在运行时检查传递给它的struct参数的类型,找到它的字段然后创建sql语句。这时就需要用到反射了。
reflect包
reflect包在Go中实现了运行时的反射
reflect包有助于识别底层的具体类型和interface{}变量的值
这正是我们所需要的。该createQuery函数接受一个interface{}参数,并且需要根据参数的具体类型和值创建查询interface{}。这正是反射包的作用。
reflect.Type 和 reflect.Value
参数 interface{} 的具体类型由 reflect.Type 表示,而 reflect.Value 表示它的具体值。reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。这两种类型是我们创建sql生成器的基础。我们现在用一个简单的例子来理解这两种类型。
从输出中,我们可以看到程序打印出接口的具体类型和值。
reflect.Kind
Kind和Type的区别
Type表示 interface{} 的实际类型,在本例中为 main.Order,Kind表示该类型的具体种类。在这种情况下,它是一个struct。
NumField()和Field() 方法
NumField()方法返回结构中的字段数,Field(i int)方法返回reflect.Value
Int() 和 String() 方法
Int和String可以帮助我们分别取出reflect.Value为int64和string的值
完整的程序:sql语句生成器
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
adress string
salary int
country string
}
func createQuery(q interface{}) {
if reflect.ValueOf(q).Kind() == reflect.Struct {
//Name returns the type's name within its package for a defined type.
//For other (non-defined) types it returns the empty string.
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 {
//Sprintf formats according to a format specifier and returns the resulting string.
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: 89,
customerId: 13,
}
createQuery(o)
p := employee{
name: "亚瑟",
id: 454,
adress: "沈阳大街",
salary: 19999,
country: "指定有你好果子次中国",
}
createQuery(p)
q := 2345
createQuery(q)
}
注意:
反射是 Go 中一个非常强大和先进的概念,应该谨慎使用。使用反射编写清晰且可维护的代码非常困难。应尽可能避免使用,仅在绝对必要时使用。