反射

什么是反射

反射是指在运行时判断变量的类型和值的一种能力。

不过你也许会好奇,既然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{}

比如,如果我们传下面的结构体变量作为入参:

那么我们期望的输出为:

或者我们传入的结构体变量为:

那么我们期望的输出为:

这里我们为了简单起见,仅考虑属性为intstring类型。未了实现一个通用性的函数createQuery,我们只能在运行时去动态的判断参数的类型和值,这就需要借助反射。

reflect

reflect包提供了我们需要的运行时的反射功能。我们可以借助它来判断interface{}变量内部具体的类型和值。这正是我们需要的功能。函数createQuery的入参就是interface{}类型,我们需要知道其内部具体类型和具体值来创建SQL语句。我么先来看下reflect都提供了哪些功能。

reflect.Type reflect.Value

reflect包中有两个方法reflect.Typeof()reflect.ValueOf()用来获取某个变量的具体类型和具体值,这两个函数的返回值类型分别是reflect.Typereflect.Value。下面通过一个例子来看看。

这里我们通过reflect.TypeOfreflect.ValueOf获取到了interface{}类型的变量q的具体类型和具体值。输出结果为:

reflect.Kind

reflect还提供了另外一个判断大类的方法reflect.Kind()。这里的大类需要好好理解下,我们可以定义多种多样的结构体具体类型,但是Golang中的数据类型包括多个大类,比如结构体、数字、字符串等等。可以通过reflect.Kind()来判断是属于哪个大类。

该程序输出为:

注意这里的变量o反射的结果,Type对应的值为main.orderKind对应的值为struct

NumField() Field()

前面我们已经看到,reflect.Value类型的变量,是调用reflect.ValueOf()的返回值。如果reflect.ValueOf()的返回值类型是结构体类型,那么有两个方法,NumField()Field(i int),分别用来获取结构体变量的属性个数,以及第i个属性值。

因为NumField()仅能作用于结构体类型,因此我们首先判断了q是否是结构体类型。该程序输出为:

Int() String()

Int()String()可分别用来将reflect.Value解析成int64string类型。

输出为:

完整程序

熟悉完了reflect包相关API后我们看下完整程序。

这里我们首先判断函数的入参是否是结构体类型。然后通过reflect.TypeOf(q).Name()获取到了具体结构体类型名称。然后我们通过一个switch case语句来处理不同的属性类型。注意这里我们为了简单起见仅考虑的intstring类型。该程序输出为:

不要滥用

Golang中的反射非常强大,但是也不要滥用,除非不得不用了再考虑使用,否则使用反射写的代码不太容易理解。

Last updated

Was this helpful?