> For the complete documentation index, see [llms.txt](https://sunwenfei.gitbook.io/sunwenfei/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/fan-she.md).

# 反射

#### 什么是反射

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

不过你也许会好奇，既然Golang是一种强类型的编程语言，所有数据的类型都是我们定义好的，为什么还要在运行时去检查变量的类型和值呢？

这种说法在大部分情况下是正确的，但不是绝对的。有些比较特殊的场景下确实需要反射这种强大的能力。典型的一个使用场景就是`ORM`，即对象关系映射。`ORM`是一种使用面向对象编程来操作数据库的模式，可以通过创建类或对象(在Golang中就是结构体类型和结构体变量)来操作数据库。

```go
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{}`。

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

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

那么我们期望的输出为：

```sql
insert into order values (1234, 567)
```

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

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

那么我们期望的输出为：

```sql
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`。下面通过一个例子来看看。

```go
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`的具体类型和具体值。输出结果为：

```
Type  main.order
Value  {456 56}
```

**reflect.Kind**

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

```go
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.order`，`Kind`对应的值为`struct`。

**NumField() Field()**

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

```go
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`解析成`int64`和`string`类型。

```go
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后我们看下完整程序。

```go
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`类型。该程序输出为：

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

#### 不要滥用

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