golang reflect 实战技巧分享(1)

2019-06-03 09:06:52 +08:00
 guonaihong

由来

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

简单玩转 reflect 好处

举个例子,在使用 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
1966 次点击
所在节点    程序员
9 条回复
guonaihong
2019-06-03 12:38:59 +08:00
再分享更多关于 reflect 的知识。
metrue
2019-06-03 12:49:09 +08:00
想看 PR
guonaihong
2019-06-03 13:03:35 +08:00
metrue
2019-06-03 13:39:10 +08:00
@guonaihong 学习了,🙏
guonaihong
2019-06-03 14:41:12 +08:00
@metrue 🙏
leon0903
2019-06-03 14:59:03 +08:00
平时工作中用了一点点 Go 的反射,觉得还是不好用,至少在反射方面和 Java 相比感觉差了很多
guonaihong
2019-06-03 16:09:36 +08:00
@leon0903 是的,go 的反射确实不好用。
HsingChih
2019-07-20 16:24:32 +08:00
谢了,正好用到
guonaihong
2019-08-23 13:35:02 +08:00
@HsingChih 这是我新起的项目,大量用到 reflect,感兴趣的话,可以一看。
https://github.com/guonaihong/gout

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/570168

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX