(摘) Golang反射 Reflect

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

Golang中的反射,应该属于必知必会的内容。

什么是反射

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

为什么要用反射

  1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

反射的实现

静态类型&动态类型

Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了,而运行时才知道变量类型的叫做动态类型
反射主要与Golang的interface类型相关,只有interface类型才有反射一说

var A interface{} // 静态类型interface{}
A = 10            // 静态类型为interface{}  动态为int
A = "String"      // 静态类型为interface{}  动态为string
var M *int
A = M             // A的值可以改变

在Go的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type),value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型(concrete type),另外一个指针指向实际的值(value)。

interface及其pair的存在,是Go实现反射的前提
反射就是用来检测存储在接口变量内部pair对的一种机制。

reflect包-实现反射

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

1. reflect.Type 和 reflect.Value

reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。
reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。

2. reflect.Kind

reflect.Type 变量 和 reflect.Value 变量都可以通过 Kind() 方法返回对应的接口变量的基础类型。

3. NumField()和Field()

NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法结构体中第i个字段的reflect.Value。

type student struct {
	id int
	name string
	age int
}

func main() {
	stu := student{id: 1001, name: "小黄", age: 16}
	value := reflect.ValueOf(stu)
	fmt.Println("字段数:", value.NumField())

	for i:=0;i<value.NumField();i++{
		fmt.Printf("第 %d 个字段:%v \n", i+1, value.Field(i))
	}
}

尽量避免用反射

  1. 与反射相关的代码,经常是难以阅读的
  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错
  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。

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

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

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

reflect.Type 就是 MyInt,而非 int,如果想获得 int 只能使用Kind()

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

对以下代码作了修改。这里只会打印出LegCount的值。将age改为Age即可修改,打印出值。

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的值
    if vLegCount.CanSet() {
		vLegCount.SetInt(4)
		fmt.Println(vLegCount.Int())
    }
    // 这里会报错
    if vAge.CanSet() {
		vAge.SetInt(3)
		fmt.Println(vAge.Int())
    }    
}

通过反射调用函数

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")  // 将Show改为Sleep可运行成功
    showMethod.Call(nil)
}

当存在此名的方法时,返回地址。例如:0x8a9a60
当不存在此名的方法时,返回

反射实现: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
}

未完待续…

相关文章