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