V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
guonaihong
V2EX  ›  程序员

golang reflect 实战技巧分享(1)

  •  
  •   guonaihong ·
    guonaihong · 2019-06-03 09:06:52 +08:00 · 2111 次点击
    这是一个创建于 1993 天前的主题,其中的信息可能已经有所发展或是发生改变。

    由来

    上个月给 gin 提交了一个 pr,主要用的是 reflect 相关的知识。
    在最好用的命令行解析库 https://github.com/guonaihong/flag (自己封的)也大量用到 reflect 让接口变的好用。
    鉴于 reflect 接口是如此的难以使用,分享下常见用法

    简单玩转 reflect 好处

    • 可以给一些开源库提交 pr
    • 可以设计出接口更好用的库

    举个例子,在使用 flag 库是,只要配置下结构体,就可以解析命令行参数

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/flag"
    	_ "os"
    )
    
    type TestOption struct {
    	Int     int      `opt:"i, int" usage:"test int"`
    	Int64   int64    `opt:"i64, int64" usage:"test int64"`
    	Strings []string `opt:"s, strings" usage:"test []string"`
    	Int64s  []int64  `opt:"i64s, int64s" usage:"test []int64"`
    
    	Int2 int `opt:"i2" usage:"test int2"`
    }
    
    func main() {
    	option := TestOption{}
    
    	flag.ParseStruct(&option)
    
    	fmt.Printf("%#v\n", option)
    }
    
    // 运行
    // go run main.go -i 3 -i64 64 -i64s 64 -i64s 1 -i64s 2 -i64s 3 -s a -s b -s c
    // 输出
    // main.TestOption{Int:3, Int64:64, Strings:[]string{"a", "b", "c"}, Int64s:[]int64{64, 1, 2, 3}, Int2:0}
    
    

    现在我们进入正题

    遍历结构体(以包含递归)

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type test2 struct {
        X1 int 
        F1 float64
    }
    
    type Test struct {
        X int 
        F float64
        test2
    }
    
    func rangeStructCore(v reflect.Value) {
    
        if v.Kind() == reflect.Struct {
            tValue := v.Type()
    
            for i := 0; i < v.NumField(); i++ {
                sf := tValue.Field(i)
    
                if sf.PkgPath != "" && !sf.Anonymous {
                    continue
                }
    
                if v.Field(i).Kind() == reflect.Struct {
                    rangeStructCore(v.Field(i))
                    continue
                }
                fmt.Printf("sf = %v: %v, %t, %v\n", sf, sf.PkgPath, sf.Anonymous, v.Field(i))
            }
        }   
    }
    
    func rangeStruct(x interface{}) {
        rangeStructCore(reflect.ValueOf(x))
    }
    
    func main() {
        rangeStruct(Test{X: 3, F: 4, test2: test2{X1: 5, F1: 6}})
    }
    // 输出 
    // sf = {X  int  0 [0] false}: , false, 3
    // sf = {F  float64  8 [1] false}: , false, 4
    // sf = {X1  int  0 [0] false}: , false, 5
    // sf = {F1  float64  8 [1] false}: , false, 6
    

    利用反射修改结构体的值

    package main
    
    import (
            "fmt"
            "reflect"
    )
    
    type Test struct {
            X int
    }
    
    func modifyValue(x interface{}) {
    
            v := reflect.ValueOf(x)
            if v.Kind() == reflect.Ptr {
                    v = v.Elem()
            }
    
            for i := 0; i < v.NumField(); i++ {
                    //sf := v.Type().Field(i)
                    sv := v.Field(i)
                    //fmt.Printf("%v#%v#%t\n", sf, sv, sv.CanAddr())
                    // 这里是重点
                    px := sv.Addr().Interface().(*int)
                    *px = 4
            }
    }
    
    func main() {
            t := Test{X: 3}
            fmt.Printf("before:%#v\n", t)
            modifyValue(&t)
            fmt.Printf("after:%#v\n", t)
    
    }
    // 输出
    // before:main.Test{X:3}
    // after:main.Test{X:4}
    
    判断类型
    // 提取类型信息到变量里
    var stringSliceType = reflect.TypeOf([]string{})
    var intSliceType = reflect.TypeOf([]int{})
    var int32SliceType = reflect.TypeOf([]int32{})
    
    func typeCheck(x interface{}) {
        // 提取 interface{}里面的类型信息
        vt := reflect.ValueOf(x).Type()
        switch vt {
        case stringSliceType:
            fmt.Printf("[]string{}\n")
        case intSliceType:
            fmt.Printf("[]int{}\n")
        case int32SliceType:
            fmt.Printf("[]int32{}\n")
        default:
            fmt.Printf("unkown type\n")
        }   
    }
    
    func main() {
        typeCheck([]string{})
        typeCheck([]int{})
        typeCheck([]int32{})
        typeCheck(0)
    }
    // 输出
    // []string{}
    // []int{}
    // []int32{}
    // unkown type
    
    
    判断指针类型和空指针
    func checkPtrAndNil(x interface{}) {
        v := reflect.ValueOf(x)
        if v.Kind() == reflect.Ptr {
            fmt.Printf("%v is pointer\n", v.Type())
            if v.IsNil() {
                fmt.Printf("%v is is a null pointer\n", v.Type())
                return
            }
    
        }   
    
        fmt.Printf("%v is value\n", v.Type())
    }
    
    func main() {
        var ip *int
        var sp *string
        var i32p *int32
    
        checkPtrAndNil(ip)
        checkPtrAndNil(sp)
        checkPtrAndNil(i32p)
        checkPtrAndNil(3)
    }
    
    // 输出
    // *int is pointer
    // *int is is a null pointer
    // *string is pointer
    // *string is is a null pointer
    // *int32 is pointer
    // *int32 is is a null pointer
    // int is value
    
    

    类型空值

    效果相当于 var t Type

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type test struct {
        X int 
        Y int 
    }
    
    func zeroValue(x interface{}) {
        v := reflect.Zero(reflect.ValueOf(x).Type())
        fmt.Printf("%v zero (%v)\n", x, v)
    }
    
    func main() {
        zeroValue("string")
        zeroValue(3)
        zeroValue(1.1)
        zeroValue(test{3, 4}) 
    }
    // 输出如下
    // string zero ()
    // 3 zero (0)
    // 1.1 zero (0)
    // {3 4} zero ({0 0})
    

    解引用

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func elem(x interface{}) {
        v := reflect.ValueOf(x)
        fmt.Printf("1.value type = %v\n", v.Type())
        if v.Kind() == reflect.Ptr {
            v = v.Elem()
        }
    
        fmt.Printf("2.value type = %v\n", v.Type())
    }
    
    func main() {
        var i int
        var f float64
    
        elem(&i)
        elem(&f)
    }
    // 输出
    // 1.value type = *int
    // 2.value type = int
    // 1.value type = *float64
    // 2.value type = float64
    

    get interface

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func getInterface(x interface{}) {
        x1 := reflect.ValueOf(x).Interface()
        fmt.Printf("x %v, x1 %v\n", x, x1)
    }
    
    func main() {
        getInterface(3)
    }
    // 输出
    // x 3, x1 3
    
    9 条回复    2019-08-23 13:35:02 +08:00
    guonaihong
        1
    guonaihong  
    OP
       2019-06-03 12:38:59 +08:00
    再分享更多关于 reflect 的知识。
    metrue
        2
    metrue  
       2019-06-03 12:49:09 +08:00
    想看 PR
    guonaihong
        3
    guonaihong  
    OP
       2019-06-03 13:03:35 +08:00
    metrue
        4
    metrue  
       2019-06-03 13:39:10 +08:00
    @guonaihong 学习了,🙏
    guonaihong
        5
    guonaihong  
    OP
       2019-06-03 14:41:12 +08:00
    @metrue 🙏
    leon0903
        6
    leon0903  
       2019-06-03 14:59:03 +08:00
    平时工作中用了一点点 Go 的反射,觉得还是不好用,至少在反射方面和 Java 相比感觉差了很多
    guonaihong
        7
    guonaihong  
    OP
       2019-06-03 16:09:36 +08:00
    @leon0903 是的,go 的反射确实不好用。
    HsingChih
        8
    HsingChih  
       2019-07-20 16:24:32 +08:00
    谢了,正好用到
    guonaihong
        9
    guonaihong  
    OP
       2019-08-23 13:35:02 +08:00
    @HsingChih 这是我新起的项目,大量用到 reflect,感兴趣的话,可以一看。
    https://github.com/guonaihong/gout
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2855 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 07:52 · PVG 15:52 · LAX 23:52 · JFK 02:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.