『Go 内置库第一季:reflect』

本节的主题是:反射 -- 采用某种机制来实现对自己行为的描述和检测,是类型的一种能力, 简单的说是能够获取数据的类型和值。

所以反射的核心包括两方面:类型(type)、值(value)

大纲:

  • 自己总结的反射的用法
  • 官方的反射的用法
  • 学到了什么

自己总结的反射的用法

既然反射的用法包括两方面,那么日常编写代码过程中,包括两个方面:获取类型、获取值

下面演示最基本的用法,而且多用在结构体这种数据类型上。

  • reflect.TypeOf
  • reflect.ValueOf
var number int
number = 1
fmt.Println(reflect.TypeOf(number), reflect.ValueOf(number))

>> int 1
type numberExample int
var numberOther numberExample
numberOther = 2
fmt.Println(reflect.TypeOf(numberOther), reflect.ValueOf(numberOther), reflect.ValueOf(numberOther).Kind())

>> main.numberExample 2 int

可以看到,如何获取数据类型,也可以看出 TypeOf 和 Kind 的区别,TypeOf 获取的是基本数据类型和以 type 定义的类型;Kind 获取的是底层的基本的数据类型,但不包括以 type 定义的类型。

但是最常见的关于反射的用法还是对结构体的处理。结构体在 Go 里面是各种数据类型的数据的集合,同时还可以具有自己的方法。

结构体定义,还存在 tag, 在结构体和 json 相互序列化和反序列化之间的起作用。

定义如下结构体:

type Info struct {
    Name   string      `json:"name"`
    Age    interface{} `json:"age"`
    Prince float32     `json:"prince"`
}

type Groups struct {
    ID        uint     `json:"id"`
    Name      string   `json:"name"`
    Count     int      `json:"count"`
    CanFly    bool     `json:"can_fly"`
    GroupIDs  []int    `json:"group_ids"`
    GroupName []string `json:"group_name"`
    Info      `json:"info"`
}

定义了两个结构体,相应的字段也声明了 tag。

初始化:

// 匿名结构体,定义全局变量

var globalValue struct {
    groups Groups
}

var valid struct {
    tag   string
    value Model
}

func init() {
    globalValue.groups = Groups{
        ID:        1,
        Name:      "xieWei",
        Count:     12,
        CanFly:    false,
        GroupIDs:  []int{100, 200, 300, 400},
        GroupName: []string{"what", "how", "when", "why"},
        Info: Info{
            Name:   "XieXiaoLu",
            Age:    20,
            Prince: 1.2345,
        },
    }

    valid.tag = "xieWei"

    valid.value = Model{
        ID:    1,
        Count: 2000,
        Name:  "Golang",
    }

}

给结构体定义两个方法,主要操作 类型和值。

func (g Groups) TypeHandler() {
}

func (g Groups) ValueHandler() {
}

对结构体的反射操作,可以获取结构体的属性的类型、值和 tag。

自己思考下,获取属性的类型、值和 tag, 作者会设计些什么内容?

获取属性、遍历属性、属性的数目、按索引获取属性

func (g Groups) TypeHandler() {
}
func (g Groups) TypeHandler() {
    typeGroups := reflect.TypeOf(g)
    name := typeGroups.Name()
    fmt.Println("Name: ", name, "Kind", typeGroups.Kind())
    for i := 0; i < typeGroups.NumField(); i++ {
        filed := typeGroups.Field(i)
        fmt.Println(filed.Name, "\t", filed.Tag, "\t", reflect.ValueOf(filed), "\t", filed.Type)
    }

    for i := 0; i < typeGroups.NumField(); i++ {
        filedByIndex := typeGroups.FieldByIndex([]int{i})
        filedByName, _ := typeGroups.FieldByName(filedByIndex.Name)
        fmt.Println(filedByIndex, filedByIndex.Name, filedByIndex.Type)
        fmt.Println(filedByName, filedByName.Name, filedByName.Type)
    }

    for i := 0; i < typeGroups.NumMethod(); i++ {
        method := typeGroups.Method(i)
        fmt.Println(method.Name, method.Type)
    }
}

操作结构体的方法:

    for i := 0; i < typeGroups.NumMethod(); i++ {
        method := typeGroups.Method(i)
        fmt.Println(method.Name, method.Type)
    }

操作值:

func (g Groups) ValueHandler() {
}
func (g Groups) ValueHandler() {
    valueGroup := reflect.ValueOf(g)
    fmt.Println(valueGroup.NumField(), valueGroup.NumMethod(), valueGroup, reflect.ValueOf(&g).Elem())

    for i := 0; i < valueGroup.NumField(); i++ {
        field := valueGroup.Field(i)
        fmt.Println(field, field.Type(), field.Kind())
    }

    method := valueGroup.MethodByName("TypeHandler")
    fmt.Println(method, method.Kind(), method.Type())

    for i := 0; i < valueGroup.NumMethod(); i++ {
        method := valueGroup.Method(i)
        fmt.Println(method.Type())
    }
    ref := reflect.ValueOf(&g).Elem()

    fmt.Println(ref.FieldByName("Name"), ref.Field(0))
}

为什么是这样的操作?

属性、值、遍历属性、遍历值

文档

为什么这么操作?

那当然看具体的 Type 的定义,是个接口。

type Type interface {
    Method(int) Method
        MethodByName(string) (Method, bool)
        NumMethod() int
        Name() string
        Kind() Kind
        Elem() Type
        Field(i int) StructField
        FieldByIndex(index []int) StructField
        FieldByName(name string) (StructField, bool)
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        NumField() int
}

可以看到,如何操作结构体属性的类型。

具体的 Value 的定义,是个结构体。无属性,有方法。

type Value struct {
    // contains filtered or unexported fields
}
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int

有时候,我们记不住 API,不知道哪些方法可以使用,怎么办?

以结构体为例?

  1. 回顾关于结构体的定义,结构体有什么?
    1. 属性
    2. 如何获取属性?按索引、按名称
    3. 属性的个数?
    4. 属性的类型?名称?
  2. 方法
    1. 方法的名称
    2. 如何获取方法
    3. 如何调用方法?
    4. 方法的个数

可以看出,严格上讲,结构体的知识点就属性(私有、公有), 方法(调用、声明)。

所以看出,作者底层的结构体的定义也是关于这些的操作。

至此,我们始终没有操作 结构体的 tag。

比如下面的结构体定义:

type Model struct {
    ID     uint   `xieWei:"number,max=10,min=1"`
    Name   string `xieWei:"string"`
    Count  int    `xieWei:"number,max=100,min=1"`
    CanFly bool   `xieWei:"bool,default=false"`
}

我们经常看到在 gin 或者 gorm 内看到这些 tag的使用。

比如:gin 中

type PostParam string {
    ID uint `form:"id" binding:"required,omitempty"`
    Name string `form:"name" binding:"required"`
    Number int `form:"number" binding:"required,eq=1|eq=2"`
}

上文根据 tag 规定字段是否必须,空值是否省略,值的范围

再比如:gorm 定义数据库表

type Student struct {
    Name string `gorm:"type:"varchar,column:name" json:"name"`
    Number int `gorm:"type:"integer,column:number" json:"number"`
}

上文根据 tag 规定字段的类型,表列的名称。

那是如何做到的呢?

答案:反射

通过反射,获取到结构体的 tag, tag 是个字符串,按照字符串的操作,比如分割操作,获取到类型等。

比如我们需要自己完成结构体的属性的类型的检验。

func (m Model) Handler(name string) bool {

    typeModel := reflect.TypeOf(m)
    if tag, ok := typeModel.FieldByName(name); ok {
        if ok := strings.HasPrefix(string(tag.Tag), valid.tag); ok {
            //fmt.Println(validTagList[0])
            validTagList := strings.FieldsFunc(string(tag.Tag), func(r rune) bool {
                return r == ',' || r == '"'
            })
            switch validTagList[1] {
            case "number":
                {
                    fmt.Println(validTagList[1:])
                }
            case "string":
                fmt.Println(validTagList[1:])

            case "bool":
                fmt.Println(validTagList[1:])

            }

        } else {
            return false
        }
    }
    return false
}

>>
[number max=10 min=1]
[string]
[number max=100 min=1]
[bool default=false]
[number min=1 max=1000]

再进行后续的操作即可。

整体的思路是:

  • 获取结构体属性的 tag
  • 把 tag 按字符串操作
  • 当然自己的校验,最好规整好结构,比如 valid:number,max=10,min=1, 统一按这样的操作,方便或许的解析。

总结:

反射是程序关于自身类型检测的一种能力,通过内置库的 reflect 可以获取到变量、结构体的类型和值,还可以设置相应的值。

关于结构体的反射是使用 reflect 的一个比较核心的用处。

如何操作:

  • 结构体有属性(公有、私有),有方法
  • 反射获取属性,可以通过遍历、也可以通过索引值、还可以通过属性名称
  • 反射获取方法,可以通过变量,也可以通过方法名称

学到了什么?

后记:学习,总想一口气全部掌握知识,实际上不科学,第一次看,你可能只能掌握 10%, 正确的做法,应该是反复看,尤其是你需要解决问题的时候。最后一定要融入自己的思考,仅仅只是涂涂画画写写,都能给你增加更多的记忆信息。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

Python3 与 C# 基础语法对比(String专栏)

Python3 与 C# 基础语法对比:https://www.cnblogs.com/dotnetcrazy/p/9102030.html

1082
来自专栏Phoenix的Android之旅

说一个小bug

这个bug其实很简单,因为String是不可变内容的,想获得新值,必须重新赋值。正确应该是

993
来自专栏轮子工厂

卧槽,为什么你的程序执行到一半就退出了,原来是因为加了这个

快到月底了,相信有很多人都和呆博一样,不是“快揭不开锅了”,而是已经快要把锅都吃了〒▽〒。没关系我们可以一起吃掉这篇精神食粮啊,营养又健康,如果觉得味道还不错,...

2892
来自专栏贺贺的前端工程师之路

正则表达式 - 学习1

开发项目的过程中,用了很多的正则表达式,可是每一次都不是自己写的,遇到正则表达式的地方,要么去求助度娘,要么就是组长给写好的,我直接贴过来然后用的。感觉真是有一...

833
来自专栏C/C++基础

C++特性使用建议

使用引用替代指针且所有不变的引用参数必须加上const。在C 语言中,如果函数需要修改变量的值,参数必须为指针,如int foo(int *pval),在 C+...

1573
来自专栏java一日一条

19 个 JavaScript 编码小技巧

这篇文章适合任何一位基于JavaScript开发的开发者。我写这篇文章主要涉及JavaScript中一些简写的代码,帮助大家更好理解一些JavaScript的基...

974
来自专栏码匠的流水账

使用kotlin改善java代码

本文只是举了kotlin可以改善java代码的几个例子,kotlin太强大了,目标是要替代java。其中很多设计可以看到scala的影子,但是黑魔法也比较多,学...

721
来自专栏java学习

Java每日一练(2017/7/21)

聊天系统 ●我希望大家积极参与答题!有什么不懂可以加小编微信进行讨论 ★珍惜每一天,拼搏每一天,专心每一天,成功每一 如果你是初学者,或者是自学者!你可以加小编...

3394
来自专栏程序员互动联盟

【编程基础】聊聊C语言-第一只螃蟹

上一篇我们介绍了开发C语言需要了解的基础术语和开发C语言常用的工具做好了进行C语言编程的准备,现在我们开始操刀烹炸C语言编程世界的第一道菜-hello wor...

35213
来自专栏飞雪无情的博客

Go语言实战笔记(二十四)| Go 反射

和Java语言一样,Go也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力。比如我们可以查看一个接口变量的具体类型,看看一个结构体有多少字段,...

951

扫码关注云+社区

领取腾讯云代金券