什么是反射
反射
是指在运行时判断变量的类型和值的一种能力。
不过你也许会好奇,既然Golang是一种强类型的编程语言,所有数据的类型都是我们定义好的,为什么还要在运行时去检查变量的类型和值呢?
这种说法在大部分情况下是正确的,但不是绝对的。有些比较特殊的场景下确实需要反射这种强大的能力。典型的一个使用场景就是ORM
,即对象关系映射。ORM
是一种使用面向对象编程来操作数据库的模式,可以通过创建类或对象(在Golang中就是结构体类型和结构体变量)来操作数据库。
Copy 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
来将一个结构体变量转变成一行SQL
语句。注意我们这里是想实现一个较为通用的函数,即可以接收任何类型的结构体变量作为入参,因此我们将参数类型声明为了interface{}
。
比如,如果我们传下面的结构体变量作为入参:
Copy o := order {
ordId: 1234,
customerId: 567
}
那么我们期望的输出为:
Copy insert into order values (1234, 567)
或者我们传入的结构体变量为:
Copy e := employee {
name: "Naveen",
id: 565,
address: "Science Park Road, Singapore",
salary: 90000,
country: "Singapore",
}
那么我们期望的输出为:
Copy insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
这里我们为了简单起见,仅考虑属性为int
或string
类型。未了实现一个通用性的函数createQuery
,我们只能在运行时去动态的判断参数的类型和值,这就需要借助反射。
reflect
包
reflect
包提供了我们需要的运行时的反射功能。我们可以借助它来判断interface{}
变量内部具体的类型和值。这正是我们需要的功能。函数createQuery
的入参就是interface{}
类型,我们需要知道其内部具体类型和具体值来创建SQL
语句。我么先来看下reflect
都提供了哪些功能。
reflect.Type reflect.Value
reflect
包中有两个方法reflect.Typeof()
和reflect.ValueOf()
用来获取某个变量的具体类型和具体值,这两个函数的返回值类型分别是reflect.Type
和reflect.Value
。下面通过一个例子来看看。
Copy 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)
}
这里我们通过reflect.TypeOf
和reflect.ValueOf
获取到了interface{}
类型的变量q
的具体类型和具体值。输出结果为:
Copy Type main.order
Value {456 56}
reflect.Kind
reflect
还提供了另外一个判断大类的方法reflect.Kind()
。这里的大类需要好好理解下,我们可以定义多种多样的结构体具体类型,但是Golang中的数据类型包括多个大类,比如结构体、数字、字符串等等。可以通过reflect.Kind()
来判断是属于哪个大类。
Copy 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)
p := 1
createQuery(p)
s := "Hello World"
createQuery(s)
}
该程序输出为:
Copy Type main.order
Kind struct
Type int
Kind int
Type string
Kind string
注意这里的变量o
反射的结果,Type
对应的值为main.order
,Kind
对应的值为struct
。
NumField() Field()
前面我们已经看到,reflect.Value
类型的变量,是调用reflect.ValueOf()
的返回值。如果reflect.ValueOf()
的返回值类型是结构体类型,那么有两个方法,NumField()
和Field(i int)
,分别用来获取结构体变量的属性个数,以及第i
个属性值。
Copy package main
import (
"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)
}
因为NumField()
仅能作用于结构体类型,因此我们首先判断了q
是否是结构体类型。该程序输出为:
Copy 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
类型。
Copy package main
import (
"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)
}
输出为:
Copy type:int64 value:56
type:string value:Naveen
完整程序
熟悉完了reflect
包相关API后我们看下完整程序。
Copy package main
import (
"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)
}
这里我们首先判断函数的入参是否是结构体类型。然后通过reflect.TypeOf(q).Name()
获取到了具体结构体类型名称。然后我们通过一个switch case
语句来处理不同的属性类型。注意这里我们为了简单起见仅考虑的int
和string
类型。该程序输出为:
Copy insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type
不要滥用
Golang中的反射非常强大,但是也不要滥用,除非不得不用了再考虑使用,否则使用反射写的代码不太容易理解。