TOC

Golang 反射简单示例

通过反射(Reflection),我们可以在程序运行时,动态的增删该一些信息(类型,方法,变量),用来完成一些特殊的任务。
主要是实现不同类型都支持的一些方法。在 ORM,序列化等方面都有应用。

这种动态获取信息的能力让程序的开发变得更加灵活,但也会损失部分性能,毕竟需要进行很多类型检查、值转换等操作。
而且,滥用的话会导致程序变得复杂,可读性下降。

示例 1

type Person struct {
    Name string
    Age  int
}

func Insert(db *sql.DB, v interface{}) error {
    value := reflect.ValueOf(v)
    typ := reflect.TypeOf(v)

    sql := "INSERT INTO persons ("
    placeholders := "VALUES ("
    for i := 0; i < typ.Elem().NumField(); i++ {
        fieldName := typ.Elem().Field(i).Name
        sql += fmt.Sprintf("%s, ", fieldName)
        placeholders += "?, "
    }
    sql = sql[:len(sql)-2] + ")"
    placeholders = placeholders[:len(placeholders)-2] + ")"

    args := make([]interface{}, typ.Elem().NumField())
    for i := 0; i < typ.Elem().NumField(); i++ {
        args[i] = value.Elem().Field(i).Interface()
    }

    _, err := db.Exec(sql+placeholders, args...)
    return err
}

示例 2

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name    string
    Age     int
    Address string
}

// 定义结构体方法
func (p Person) Hello() string {
    return "Hello, " + p.Name
}

func main() {
    p := Person{
        Name:    "Alice",
        Age:     30,
        Address: "123 Main St",
    }

    fmt.Println("获取变量的反射类型:===============")
    pType := reflect.TypeOf(p)                  // &reflect.rtype
    fmt.Printf("Type: %s, %#v\n", pType, pType) // main.Person
    // fmt.Println("Type:", pType.Elem()) // panic: reflect: Elem of invalid type main.Person
    pType2 := reflect.TypeOf(&p)
    fmt.Printf("Type: %s, %#v\n", pType2, pType2)               // *main.Person
    fmt.Printf("Type: %s, %#v\n", pType2.Elem(), pType2.Elem()) // main.Person (指针类型的基础类型)

    fmt.Println("获取变量的反射值:===============")
    pValue := reflect.ValueOf(p) // main.Person
    fmt.Printf("Value: %s, %#v\n", pValue, pValue)
    pValue2 := reflect.ValueOf(&p)
    fmt.Printf("Value: %s, %#v\n", pValue2, pValue2) // 地址
    fmt.Printf("Value: %s, %#v\n", pValue2.Elem(), pValue2.Elem())

    fmt.Println("遍历结构体字段:===============")
    for i := 0; i < pType.NumField(); i++ {
        field := pType.Field(i)
        value := pValue.Field(i)
        fmt.Printf("%s: %v\n", field.Name, value.Interface())
    }

    fmt.Println("修改结构体字段值:===============")
    ageField1 := pValue.FieldByName("Age")
    fmt.Printf("Value: %#v\n", ageField1)
    // ageField1.SetInt(31) // panic: reflect: reflect.Value.SetInt using unaddressable value
    // ageField1.Elem().SetInt(31) // panic: reflect: call of reflect.Value.Elem on int Value
    ageField2 := pValue2.Elem().FieldByName("Age")
    fmt.Printf("Value: %#v\n", ageField2)
    ageField2.SetInt(31) // 只有指针类型才能通过反射修改
    fmt.Println("New Age:", p.Age)

    fmt.Println("调用结构体方法:===============")
    helloMethod := pValue.MethodByName("Hello")
    result := helloMethod.Call(nil)
    fmt.Println(result)

    // panic: reflect: call of reflect.Value.FieldByName on ptr Value
    pValue2.FieldByName("Age")

    // panic: reflect: reflect.Value.SetInt using unaddressable value
    // a := reflect.Indirect(pValue)
    b := reflect.Indirect(pValue2)
    b.FieldByName("Age").SetInt(33)
    fmt.Printf("%#v\n", b)
}
  • reflect.TypeOf:获取变量的类型
  • reflect.ValueOf:获取变量的值

  • Type.NumField:获取结构体字段数量
  • Type.Field:获取结构体字段信息

  • Value.Field:获取结构体字段的值

  • Value.FieldByName:根据字段名获取结构体字段的值
  • Value.SetInt:设置整数类型字段的值
  • Value.MethodByName:根据方法名获取结构体方法
  • Value.Call:调用结构体方法