反射

什么是反射

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

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

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

o := order {  
    ordId: 1234,
    customerId: 567
}

那么我们期望的输出为:

insert into order values (1234, 567)

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

e := employee {
    name: "Naveen",
    id: 565,
    address: "Science Park Road, Singapore",
    salary: 90000,
    country: "Singapore",
}

那么我们期望的输出为:

insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")

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

reflect

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

reflect.Type reflect.Value

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

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.TypeOfreflect.ValueOf获取到了interface{}类型的变量q的具体类型和具体值。输出结果为:

Type  main.order
Value  {456 56}

reflect.Kind

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

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)
}

该程序输出为:

Type  main.order
Kind  struct
Type  int
Kind  int
Type  string
Kind  string

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

NumField() Field()

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

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是否是结构体类型。该程序输出为:

Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56

Int() String()

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

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)
}

输出为:

type:int64 value:56
type:string value:Naveen

完整程序

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

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语句来处理不同的属性类型。注意这里我们为了简单起见仅考虑的intstring类型。该程序输出为:

insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type

不要滥用

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

Last updated