Go语言笔记16-Reflection

什么是反射?

反射就是程序能够在运行时检查变量和值,求出它们的类型。

为何需要检查变量,确定变量的类型?

举个例子来说明需要在运行时求得变量类型的情况:我们需要编写一个简单的函数,它接收结构体作为参数,并用它来创建一个SQL插入查询。

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() {
    o := order{1234, 567}
    // insert into order values(1234, 567)
    fmt.Println(createQuery(o))

    e := employee{"Naveen", 565, "Science Park Road", 90000, "Singapore"}
    // insert into employee values("Naveen", 565, "Science Park Road", 90000, "Singapore")
    fmt.Println(createQuery(e))
}

如上代码,我们的目标就是完成createQuery函数,我们想让它变得通用,可以接收任何结构体作为参数,根据结构体的字段创建插入查询。如main函数所示,不同的结构体输出对应的SQL语句。

createQuery函数应该适用于所有的结构体。因此,要编写这个函数,就必须在运行时检查传递过来的结构体参数的类型,找到结构体字段,接着创建查询。这时就需要用到反射了。

reflect包

reflect实现了运行时反射。reflect包会帮助识别interface{}变量的底层具体类型和具体值。

reflect包中的几种类型和方法:

  • reflect.Type表示interface{}的具体类型,而reflect.Value表示它的具体值。
  • reflect.TypeOf()reflect.ValueOf()两个函数可以分别返回reflect.Typereflect.Value
type order struct {
    ordId      int
    customerId int
}

func main() {
    o := order{1234, 567}
    fmt.Println(reflect.TypeOf(o))  // main.order
    fmt.Println(reflect.ValueOf(o)) // {1234 567}
}
  • 如下所示,reflect.Type表示interface{}的实际类型(这里是main.order),而reflect.Kind表示该类型的特定类别(这里是struct)。
type order struct {
    ordId      int
    customerId int
}

func main() {
    o := order{1234, 567}
    fmt.Println(reflect.TypeOf(o))         // main.order
    fmt.Println(reflect.TypeOf(o).Kind())  // struct
    fmt.Println(reflect.ValueOf(o))        // {1234 567}
    fmt.Println(reflect.ValueOf(o).Kind()) // struct
}
  • NumField()方法返回结构体中字段的数量,而Field(i int)方法返回字段ireflect.Value
type order struct {
    ordId      int
    customerId int
}

func main() {
    o := order{1234, 567}
    // NumField方法只能在结构体上使用,所以先检查o的类别是struct
    if reflect.ValueOf(o).Kind() == reflect.Struct {
        v := reflect.ValueOf(o)
        fmt.Println("Number of fields:", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Println("Field:", i,
                "Type:", reflect.TypeOf(o).Field(i).Name,
                "Value:", v.Field(i))
        }
    }
    /*
    Number of fields: 2
    Field: 0 Type: ordId Value: 1234
    Field: 1 Type: customerId Value: 567
    */
}
  • Int()String()可以帮助我们分别取出reflect.Value作为int64string

完整的程序

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() // order
        query := fmt.Sprintf("insert into %s values(", t) // "insert into order values("
        v := reflect.ValueOf(q) // {1234 567}
        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{456, 56}
    createQuery(o)

    e := employee{"Naveen", 565, "Coimbatore", 90000, "India"}
    createQuery(e)

    i := 90
    createQuery(i)
    /*
    insert into order values(456, 56)
    insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
    unsupported type
    */
}

应该使用反射吗?

反射是Go语言中非常强大和高级的概念,我们应该小心谨慎地使用它。使用反射编写清晰和可维护的代码是十分困难的。我们应该尽可能避免使用它,只在必须用到它时,才使用反射。

reference:

https://studygolang.com/articles/13178