Golang反射模块reflect使用方式示例详解

Golang的反射功能,在很多场景都会用到,最基础的莫过于rpcormjson的编解码,更复杂的可能会到做另外一门语言的虚拟机。通过反射模块,我们可以在编程语言的runtime运行时期间去访问内部产生对象的信息。了解反射模块的实现,对我们了解Golang对象机制本身,也是莫大的帮助。

今天,恰逢阳康+新年,就决定来探究一下Golang的反射模块——reflect

从最基础的开始,reflect模块,以获取整数对象的类型信息为例,我们可以这么用:

func TestReflect_Integer(t *testing.T) {
    i := 1
    v := reflect.ValueOf(i)
    vKind := v.Kind()
    vType := v.Type()
    t.Logf("i kind: %+v\n", vKind)
    t.Logf("i type: %+v\n", vType)

    itf := v.Interface()
    j, ok := itf.(int)
    t.Logf("j val: %+v\n", j)
    if !ok || j != i {
        t.Fatalf("i != j")
    }
}

reflect.ValueOf的入参是interface{}类型,新版本也叫做any,得到的返回值是reflect.Value类型的对象,可以认为是对原对象的一个描述性质的对象(元对象,233)。reflect.Value包含几个成员:

  • typ:原对象类型的元信息
  • ptr:指向原对象值的原生指针
  • flag:原对象类型的正整数标识

当调用KindType接口,四舍五入就是获取了reflect.Value对象的flagtyp成员实例了。

要把reflect.Value转换回原对象,首先需要通过Interface方法转化成interface{}类型的对象,再通过.(int)强转逻辑去转化成原对象。但这里需要注意下,如果真需要用到reflect反射功能且涉及到一些看似要“强转”的场景,可能是没有必要真的在代码中强转回特定类型对象的。好比rpc的调用,实质是在声明接口方法的基础上,把接口方法变成reflect.Value对象,再用Func.Call方法做函数调用。这里,也给个example

假设我们定义User结构体,内部嵌套UserInfo结构体,均有json标注,然后定义了ToStringResponse两个方法,大概长这样:

type UserInfo struct {
    Desc string `json:"desc"`
    City string `json:"city" desc:"the city of user"`
}

type User struct {
    ID   int       `json:"id"`
    Name string    `json:"name"`
    Info *UserInfo `json:"info"`
}

func (u *User) ToString() string {
    return fmt.Sprintf("[%d]%s", u.ID, u.Name)
}

func (u *User) Response(from string, msg string) string {
    return fmt.Sprintf("User %s received msg %s from <%s>", u.ToString(), msg, from)
}

那么比如Response方法,实际上也能够这样调用:

func TestReflect_Method(t *testing.T) {
    u := &User{1, "jack", nil}
    uPtr := reflect.ValueOf(u)
    // MethodByName:获取特定名字的Method
    meth, ok := uPtr.Type().MethodByName("Response")
    if !ok {
        t.Fatalf("no method named Response")
    }
    t.Logf("meth Response: %+v\n", meth)

    methType := meth.Type
    // 入参3个:User实例、from、msg
    if methType.NumIn() != 3 {
        t.Fatalf("invalid NumIn %d, expected %d", methType.NumIn(), 3)
    }
    // 返回值1个:response string
    if methType.NumOut() != 1 {
        t.Fatalf("invalid NumOut %d, expected %d", methType.NumOut(), 1)
    }
    // 通过Func.Call得到返回值list
    from, msg := reflect.ValueOf("client"), reflect.ValueOf("ping")
    rets := meth.Func.Call([]reflect.Value{uPtr, from, msg})
    if len(rets) != 1 {
        t.Fatalf("invalid num rets %d, expected %d", len(rets), 1)
    }
    // 返回1个string对象
    respVal := rets[0]
    if respVal.Type() != reflect.TypeOf("") {
        t.Fatalf("invalid ret type %v, expected %s", respVal.Type(), "STRING")
    }
    resp, ok := respVal.Interface().(string)
    if !ok {
        t.Fatalf("ret value cannot be converted to string")
    }
    t.Logf("resp: %s\n", resp)
}

通过MethodByName方法,可以定位到一个对象下的某个名字的方法实例,通过对方法实例调用Func.Call,就能实际实现对方法的调用,得到返回值列表。

涉及到指针对象的反射值,可以通过reflect.Indirect(反射值)或者反射值.Elem()的方式,获取到指针指向实例的反射值。example代码如下,因为上面我们定义ToStringResponse方法绑定的是指针对象,在这样的条件下,指针指向实例的反射值就拿不到ToString方法了,打印便知:

func TestReflect_Pointer(t *testing.T) {
    u := &User{1, "jack", nil}
    vPtr := reflect.ValueOf(u)
    vPtrKind := vPtr.Kind()
    vPtrType := vPtr.Type()
    t.Logf("ptr kind: %+v\n", vPtrKind)
    t.Logf("ptr type: %+v\n", vPtrType)
    meth, ok := vPtrType.MethodByName("ToString")
    t.Logf("ptr meth ToString: %+v (%+v)\n", meth, ok)

    vVal := reflect.Indirect(vPtr)
    // vVal := vPtr.Elem()
    vValKind := vVal.Kind()
    vValType := vVal.Type()
    t.Logf("val kind: %+v\n", vValKind)
    t.Logf("val type: %+v\n", vValType)
    meth, ok = vValType.MethodByName("ToString")
    t.Logf("val meth ToString: %+v (%+v)\n", meth, ok)
}

再进一步来看,对于slicemap这类对象,也可以通过reflect.Value内置的一些方法,访问到内部的对象。假设我们要实现slicemap的复制操作,用纯反射的方式也可以实现:

func TestReflect_CopySliceAndMap(t *testing.T) {
    mp := map[string]int{
        "jack": 1,
        "tom":  2,
    }
    sl := []int{1, 1, 2, 3, 5, 8}
    vals := []reflect.Value{reflect.ValueOf(mp), reflect.ValueOf(sl)}
    var copyVals []reflect.Value

    for _, val := range vals {
        var copyVal reflect.Value
        switch val.Kind() {
        case reflect.Map:
            // MakeMap:创建map实例
            copyVal = reflect.MakeMap(val.Type())
            // MapRange:获取map对象的Iterator
            iter := val.MapRange()
            for iter.Next() {
                copyVal.SetMapIndex(iter.Key(), iter.Value())
            }
        case reflect.Slice:
            // AppendSlice:在一个slice的基础上extend另一个slice
            copyVal = reflect.AppendSlice(
                reflect.MakeSlice(val.Type(), 0, val.Len()),
                val)
        }
        copyVals = append(copyVals, copyVal)
    }

    // 通过DeepEqual方法,可以做值的相等性比较
    for _, val := range copyVals {
        switch val.Kind() {
        case reflect.Map:
            if val.Len() != len(mp) {
                t.Fatalf("invalid map length %d, expected %d", val.Len(), len(mp))
            }
            copyVal, ok := val.Interface().(map[string]int)
            if !ok {
                t.Fatalf("map convert failed")
            }
            t.Logf("copied map: %+v", copyVal)
            for k, v := range mp {
                copyV, ok := copyVal[k]
                if !ok || !reflect.DeepEqual(v, copyV) {
                    t.Fatalf("copy value of key %s failed, expected %d, actual %d", k, v, copyV)
                }
            }
        case reflect.Slice:
            if val.Len() != len(sl) {
                t.Fatalf("invalid slice length %d, expected %d", val.Len(), len(sl))
            }
            copyVal, ok := val.Interface().([]int)
            if !ok {
                t.Fatalf("slice convert failed")
            }
            t.Logf("copied slice: %+v", copyVal)
            if !reflect.DeepEqual(copyVal, sl) {
                t.Fatalf("slice not equal")
            }
        }
    }
}

最后,也看一下reflect作用到struct定义的一些使用方法,这里就需要看reflect.Value.Type()返回的Type实例有什么功能了。对于Type实例来讲,我们可以遍历所有结构体字段定义,甚至是访问标注信息,比如jsonorm的编解码,就极度依赖这些字段的标注信息。这里的实现,也给个example

func TestReflect_Struct(t *testing.T) {
    st := reflect.ValueOf(&User{
        ID:   9,
        Name: "Ronaldo",
        Info: &UserInfo{
            Desc: "SC",
            City: "Madrid",
        },
    }).Elem().Type()
    // 多少个字段
    numField := st.NumField()
    t.Logf("num fields: %d", numField)
    // 一个个字段遍历,输出字段名字、数据类型、json标注
    for i := 0; i < numField; i++ {
        field := st.Field(i)
        t.Logf("field %d -> name: %s, type: %v, json: %s",
            i+1,
            field.Name,
            field.Type,
            field.Tag.Get("json"))
    }
    // 嵌套的字段,用FieldByIndex可以定位到
    cityField := st.FieldByIndex([]int{2, 1})
    // 按照上面UserInfo.City的定义,拿取desc标注信息
    cityFieldDesc, ok := cityField.Tag.Lookup("desc")
    if !ok {
        t.Fatalf("cannot find city field desc")
    }
    t.Logf("CityField -> name: %s, type: %v, desc: %s",
        cityField.Name,
        cityField.Type,
        cityFieldDesc)
}

可以看到,我们可以很方便拿到结构体的定义信息,更加深层嵌套的定义信息也都能拿到。可以说,太灵活了!

到此这篇关于Golang反射模块reflect使用方式探索的文章就介绍到这了,更多相关Golang反射模块reflect内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解Golang的反射reflect示例

    目录 编程语言中反射的概念 interface 和 反射 Golang的反射reflect reflect的基本功能TypeOf和ValueOf 说明 从relfect.Value中获取接口interface的信息 已知原有类型[进行“强制转换”] 说明 未知原有类型[遍历探测其Filed] 说明 通过reflect.Value设置实际变量的值 说明 通过reflect.ValueOf来进行方法的调用 说明 Golang的反射reflect性能 小结 总结 参考链接 编程语言中反射的概念 在计算

  • 详解Golang利用反射reflect动态调用方法

    编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 每种语言的反射模型都不同,并且有些语言根本不支持反射.Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用. 多插一句,

  • golang 如何用反射reflect操作结构体

    背景 需要遍历结构体的所有field 对于exported的field, 动态set这个field的value 对于unexported的field, 通过强行取址的方法来获取该值(tricky?) 思路 下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多 simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针.然后解引用. 接下来遍历结构体的每个field, exported字段是

  • Golang反射模块reflect使用方式示例详解

    Golang的反射功能,在很多场景都会用到,最基础的莫过于rpc.orm跟json的编解码,更复杂的可能会到做另外一门语言的虚拟机.通过反射模块,我们可以在编程语言的runtime运行时期间去访问内部产生对象的信息.了解反射模块的实现,对我们了解Golang对象机制本身,也是莫大的帮助. 今天,恰逢阳康+新年,就决定来探究一下Golang的反射模块——reflect. 从最基础的开始,reflect模块,以获取整数对象的类型信息为例,我们可以这么用: func TestReflect_Integ

  • Golang 官方依赖注入工具wire示例详解

    目录 依赖注入是什么 开源选型 wire providers injectors 类型区分 总结 依赖注入是什么 Dependency Injection is the idea that your components (usually structs in go) should receive their dependencies when being created. 在 Golang 中,构造一个结构体常见的有两种方式: 在结构体初始化过程中,构建它的依赖: 将依赖作为构造器入参,传入进

  • 利用Python中xlwt模块操作excel的示例详解

    目录 一.安装 二.创建表格并写入 三.设置单元格样式 四.设置单元格宽度 五.设置单元格背景色 六.设置单元格内容对齐方式 七.单元格添加超链接 八.单元格添加公式 九.单元格中输入日期 十.合并行和列 十一.单元格添加边框 一.安装 pip install xlwt 二.创建表格并写入 import xlwt # 创建一个workbook并设置编码 workbook = xlwt.Workbook(encoding = 'utf-8') # 添加sheet worksheet = workb

  • Golang 实现 RTP音视频传输示例详解

    目录 引言 RTP 数据包头部字段 Golang 的相关实现 结尾 引言 在 Coding 之前我们先来简单介绍一下 RTP(Real-time Transport Protocol), 正如它的名字所说,用于互联网的实时传输协议,通过 IP 网络传输音频和视频的网络协议. 由音视频传输工作小组开发,1996 年首次发布,并提出了以下使用设想. 简单的多播音频会议 使用 IP 的多播服务进行语音通信.通过某种分配机制,获取多播组地址和端口对.一个端口用于音频数据的,另一个用于控制(RTCP)包,

  • go语言reflect.Type 和 reflect.Value 应用示例详解

    目录 一.使用 reflect.Type 创建实例 二.使用 reflect.Value 调用函数 一.使用 reflect.Type 创建实例 在通过 reflect.TypeOf 函数获取到变量的反射类型对象之后,可以通过反射类型对象 reflect.Type 的 New 函数来创建一个新的实例,注意这个实例的类型是 reflect.Type 类型的. package main import ( "fmt" "reflect" ) func main() { v

  • Golang中的错误处理的示例详解

    目录 1.panic 2.包装错误 3.错误类型判断 4.错误值判断 1.panic 当我们执行panic的时候会结束下面的流程: package main import "fmt" func main() { fmt.Println("hello") panic("stop") fmt.Println("world") } 输出: go run 9.go hellopanic: stop 但是panic也是可以捕获的,我们可

  • JS中原始值和引用值的储存方式示例详解

    在ECMAscript中,变量可以存放两种类型的值,即原始值和引用值 原始值指的是代表原始数据类型的值,也叫基本数据类型,包括:Number.Stirng.Boolean.Null.Underfined 引用值指的是复合数据类型的值,包括:Object.Function.Array.Date.RegExp 根据数据类型不同,有的变量储存在栈中,有的储存在堆中.具体区别如下: 原始变量及他们的值储存在栈中,当把一个原始变量传递给另一个原始变量时,是把一个栈房间的东西复制到另一个栈房间,且这两个原始

  • C#获取本地IP的四种方式示例详解

    1.第一种方式 采用System.Net.Dns的GetHostAddress的方式,具体请看代码: /// <summary> /// 网络不通畅可以获取 /// 不过能获取到具体的IP /// </summary> /// <returns></returns> public static List<IPAddress> GetByGetHostAddresses() { try { IPAddress[] adds = Dns.GetHos

  • node运行js获得输出的三种方式示例详解

    一.通过console.log输出(我最喜欢的) 1.js脚本 1.js var arguments = process.argv.splice(2); //获得入参 var a= arguments[0]; 取第一个 console.log(a) //输出 2.python脚本 test_1.py import os print(os.popen('node 1.js fuck').read()) #打印结果fuck 二.通过文件读写获取 1.js脚本 1.js //npm环境别忘了装了 va

  • python模块shutil函数应用示例详解教程

    目录 本文大纲 知识串讲 1)模块导入 2)复制文件 3)复制文件夹 4)移动文件或文件夹 5)删除文件夹(慎用) 6)创建和解压压缩包 本文大纲 os模块是Python标准库中一个重要的模块,里面提供了对目录和文件的一般常用操作.而Python另外一个标准库--shutil库,它作为os模块的补充,提供了复制.移动.删除.压缩.解压等操作,这些 os 模块中一般是没有提供的.但是需要注意的是:shutil 模块对压缩包的处理是调用 ZipFile 和 TarFile这两个模块来进行的. 知识串

随机推荐