和 Java 语言一样,Go 也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力。

reflect
reflect.TypeOfreflect.ValueOf
reflect.Typereflect.Value

6hzQxX

golang 反射注意

golang 反射不能获取和修改 私有的属性以及方法

ValueOf(*ptr) 方法传递的参数必须是 指针类型 才可以修改字段否则会报错

Type 和 TypeOf

reflect.Type 类型是一个接口类型,内部指定了若干方法,通过这些方法我们可以获取到反射类型的各种信息,例如:字段、方法等

reflect.Type

1. 理解 Type 和 种类 Kind

reflect.Type 是变量的类型,而不是追根溯源的最底层类型

type MyInt int
reflect.TypeOf(MyInt).Kind()
MyInt

总结:Type 表示的是静态类型,而 kind 表示的是底层真实的类型

2. 获取类型名以及 kind

package main

import (
    "fmt"
    "reflect"
)

// 定义一个 MyInt 类型
type MyInt int

func main() {
    // 声明一个空结构体
    type cat struct {
    }
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(cat{})
    // 显示反射类型对象的名称和种类
    fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
    // 获取Zero常量的反射类型对象
    typeOfA := reflect.TypeOf(Zero)
    // 显示反射类型对象的名称和种类
    fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

cat struct

MyInt int

3. Type 常用方法

获取与成员相关的方法如下:

方法描述
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机
NumField() int返回结构体成员字段数量(包含私有字段)
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值

StructField结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等

type StructField struct {
    Name      string    // 字段名
    PkgPath   string    // 字段路径
    Type      Type      // 字段反射类型对象
    Tag       StructTag // 字段的结构体标签
    Offset    uintptr   // 字段在结构体中的相对偏移
    Index     []int     // Type.FieldByIndex中的返回的索引值
    Anonymous bool      // 是否为匿名字段
}

4. 获取成员反射信息

package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明一个空结构体
    type cat struct {
        Name string
        // 带有结构体tag的字段
        Type int `json:"type" id:"100"`
    }
    // 创建cat的实例
    ins := cat{Name: "mimi", Type: 1}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(ins)
    // 遍历结构体所有成员
    for i := 0; i < typeOfCat.NumField(); i++ {
        // 获取每个成员的结构体字段类型
        fieldType := typeOfCat.Field(i)
        // 输出成员名和tag
        fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)
    }
    // 通过字段名, 找到字段类型信息
    if catType, ok := typeOfCat.FieldByName("Type"); ok {
        // 从tag中取出需要的tag
        fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
    }
}

5. 通过类型信息创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针

func main() {
    var a int
    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)
    // 根据反射类型对象创建类型实例
    aIns := reflect.New(typeOfA)
    // 输出:*int ptr
    fmt.Println(aIns.Type(), aIns.Kind())
}

Value 和 ValueOf

reflect.ValueOf

1. 生成原始类型的对象

可以通过下面几种方法从反射值对象 reflect.Value 中获取原值

方法名说 明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回

代码演示如下👇

func main() {
    // 声明整型变量a并赋初值
    var a int = 1024
    // 获取变量a的反射值对象
    valueOfA := reflect.ValueOf(a)
    // 获取interface{}类型的值, 通过类型断言转换
    var getA int = valueOfA.Interface().(int)
    // 获取64位的值, 强制类型转换为int类型
    var getA2 int = int(valueOfA.Int())
    fmt.Println(getA, getA2)
}

2. 操作结构体成员的值

反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,方法列表参考 Type 常用方法

修改成员的值 使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果该对象不可寻址或者成员是私有的,则无法修改对象值

判定是否可以操作的方法有如下👇

*&

修改的方法如下👇

Set(x Value)将值设置为传入的反射值对象的值
Setlnt(x int64)使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64)使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64)使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool)使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte)设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string)设置字符串值。当值的类型不是 string 时会发生宕机

代码案例如下

func main() {
    type dog struct {
        LegCount int
        age int
    }
    // 获取dog实例地址的反射值对象
    valueOfDog := reflect.ValueOf(&dog{})
    // 取出dog实例地址的元素
    valueOfDog = valueOfDog.Elem()
    // 获取legCount字段的值
    vLegCount := valueOfDog.FieldByName("LegCount")
    vAge := valueOfDog.FieldByName("age")
    // 尝试设置legCount的值
    vLegCount.SetInt(4)
    // 这里会报错
    vAge.SetInt(4)
    fmt.Println(vLegCount.Int())
}

通过反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数,使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回

package main
import (
    "fmt"
    "reflect"
)
// 普通函数
func add(a, b int) int {
    return a + b
}
func main() {
    // 将函数包装为反射值对象
    funcValue := reflect.ValueOf(add)
    // 构造函数参数, 传入两个整型值
    paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
    // 反射调用函数
    retList := funcValue.Call(paramList)
    // 获取第一个返回值, 取整数值
    fmt.Println(retList[0].Int())
}

通过反射调用对象中的方法

如果反射值对象中具有方法时,可以通过反射调用方法,获取方法如下👇

方法描述
Method(i int) Value根据索引,返回索引对应的方法
NumMethod() int返回结构体成员方法(包含私有)
MethodByName(name string) Value根据给定字符串返回字符串对应的结构体方法
package main

import (
    "fmt"
    "reflect"
)
type Cat struct {
    Name string
}

func (c *Cat) Sleep() {
    fmt.Println("呜呜呜...")
}

func main() {
    cat := Cat{}
    valueOf := reflect.ValueOf(&cat)
    showMethod := valueOf.MethodByName("Show")
    showMethod.Call(nil)
}

反射实现:map 转 struct

func Map2Struct(m map[string]interface{}, obj interface{}) {
    value := reflect.ValueOf(obj)

    // obj 必须是指针且指针指向的必须是 struct
    if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct {
        value = value.Elem()
        getMapName := func(key string) interface{} {
            for k, v := range m {
                if strings.EqualFold(k, key) {
                    return v
                }
            }
            return nil
        }
        // 循环赋值
        for i := 0; i < value.NumField(); i++ {
            // 获取字段 type 对象
            field := value.Field(i)
            if !field.CanSet() {
                continue
            }
            // 获取字段名称
            fieldName := value.Type().Field(i).Name
            fmt.Println("fieldName -> ", fieldName)
            // 获取 map 中的对应的值
            fieldVal := getMapName(fieldName)
            if fieldVal != nil {
                field.Set(reflect.ValueOf(fieldVal))
            }
        }
    } else {
        panic("must prt")
    }

}

反射实现:struct 转 map

func Struct2Map(obj interface{}) map[string]interface{} {
    value := reflect.ValueOf(obj)

    if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
        panic("must prt")
    }
    value = value.Elem()
    t := value.Type()

    // 创建 map
    resultMap := make(map[string]interface{})

    // 循环获取字段名称以及对应的值
    for i := 0; i < value.NumField(); i++ {
        val := value.Field(i)
        typeName := t.Field(i)
        if !val.CanSet() {
            resultMap[typeName.Name] = reflect.New(typeName.Type).Elem().Interface()
            continue
        }
        resultMap[typeName.Name] = val.Interface()
    }

    return resultMap
}