• 技术文章 >后端开发 >Golang

    go语言中反射三定律是什么

    青灯夜游青灯夜游2023-01-28 09:04:28原创65

    反射三定律:1、反射可以将“接口类型变量”转换为“反射类型对象”,这里反射类型指“reflect.Type”和 “reflect.Value”;2、反射可以将“反射类型对象”转换为“接口类型变量”;3、如果要修改“反射类型对象”其值必须是“可写的”。

    本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。

    在反射的世界里,我们拥有了获取一个对象的类型,属性及方法的能力。

    1.png

    两种类型:Type 和 Value

    在 Go 反射的世界里,有两种类型非常重要,是整个反射的核心,在学习 reflect 包的使用时,先得学习下这两种类型:

    它们分别对应着真实世界里的 type 和 value,只不过在反射对象里,它们拥有更多的内容。

    从源码上来看,reflect.Type 是以一个接口的形式存在的

    type Type interface {
        Align() int
        FieldAlign() int
        Method(int) Method
        MethodByName(string) (Method, bool)
        NumMethod() int
        Name() string
        PkgPath() string
        Size() uintptr
        String() string
        Kind() Kind
        Implements(u Type) bool
        AssignableTo(u Type) bool
        ConvertibleTo(u Type) bool
        Comparable() bool
        Bits() int
        ChanDir() ChanDir
        IsVariadic() bool
        Elem() Type
        Field(i int) StructField
        FieldByIndex(index []int) StructField
        FieldByName(name string) (StructField, bool)
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        In(i int) Type
        Key() Type
        Len() int
        NumField() int
        NumIn() int
        NumOut() int
        Out(i int) Type
        common() *rtype
        uncommon() *uncommonType
    }

    而 reflect.Value 是以一个结构体的形式存在,

    type Value struct {
        typ *rtype
        ptr unsafe.Pointer
        flag
    }

    同时它接收了很多的方法(见下表),这里出于篇幅的限制这里也没办法一一介绍。

    Addr
    Bool
    Bytes
    runes
    CanAddr
    CanSet
    Call
    CallSlice
    call
    Cap
    Close
    Complex
    Elem
    Field
    FieldByIndex
    FieldByName
    FieldByNameFunc
    Float
    Index
    Int
    CanInterface
    Interface
    InterfaceData
    IsNil
    IsValid
    IsZero
    Kind
    Len
    MapIndex
    MapKeys
    MapRange
    Method
    NumMethod
    MethodByName
    NumField
    OverflowComplex
    OverflowFloat
    OverflowInt
    OverflowUint
    Pointer
    Recv
    recv
    Send
    send
    Set
    SetBool
    SetBytes
    setRunes
    SetComplex
    SetFloat
    SetInt
    SetLen
    SetCap
    SetMapIndex
    SetUint
    SetPointer
    SetString
    Slice
    Slice3
    String
    TryRecv
    TrySend
    Type
    Uint
    UnsafeAddr
    assignTo
    Convert

    通过上一节的内容(),我们知道了一个接口变量,实际上都是由一 pair 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界里,type 和 value 是合并在一起组成 接口变量的。

    而在反射的世界里,type 和 data 却是分开的,他们分别由 reflect.Type 和 reflect.Value 来表现。

    解读反射的三大定律

    Go 语言里有个反射三定律,是你在学习反射时,很重要的参考:

    翻译一下,就是:

    第一定律

    Reflection goes from interface value to reflection object.

    为了实现从接口变量到反射对象的转换,需要提到 reflect 包里很重要的两个方法:

    这两个方法返回的对象,我们称之为反射对象:Type object 和 Value object。

    2.png

    举个例子,看下这两个方法是如何使用的?

    package main
    
    import (
    "fmt"
    "reflect"
    )
    
    func main() {
        var age interface{} = 25
    
        fmt.Printf("原始接口变量的类型为 %T,值为 %v \n", age, age)
    
        t := reflect.TypeOf(age)
        v := reflect.ValueOf(age)
    
        // 从接口变量到反射对象
        fmt.Printf("从接口变量到反射对象:Type对象的类型为 %T \n", t)
        fmt.Printf("从接口变量到反射对象:Value对象的类型为 %T \n", v)
    
    }

    输出如下

    原始接口变量的类型为 int,值为 25 从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
    从接口变量到反射对象:Value对象的类型为 reflect.Value 复制代码

    如此我们完成了从接口类型变量到反射对象的转换。

    等等,上面我们定义的 age 不是 int 类型的吗?第一法则里怎么会说是接口类型的呢?

    关于这点,其实在上一节(关于接口的三个 『潜规则』)已经提到过了,由于 TypeOf 和 ValueOf 两个函数接收的是 interface{} 空接口类型,而 Go 语言函数都是值传递,因此Go语言会将我们的类型隐式地转换成接口类型。

    原始接口变量的类型为 int,值为 25 
    从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
    从接口变量到反射对象:Value对象的类型为 reflect.Value

    第二定律

    Reflection goes from reflection object to interface value.

    和第一定律刚好相反,第二定律描述的是,从反射对象到接口变量的转换。

    3.png

    通过源码可知, reflect.Value 的结构体会接收 Interface 方法,返回了一个 interface{} 类型的变量(注意:只有 Value 才能逆向转换,而 Type 则不行,这也很容易理解,如果 Type 能逆向,那么逆向成什么呢?

    // Interface returns v's current value as an interface{}.
    // It is equivalent to:
    //    var i interface{} = (v's underlying value)
    // It panics if the Value was obtained by accessing
    // unexported struct fields.
    func (v Value) Interface() (i interface{}) {
        return valueInterface(v, true)
    }

    这个函数就是我们用来实现将反射对象转换成接口变量的一个桥梁。

    例子如下

    package main
    
    import (
    "fmt"
    "reflect"
    )
    
    func main() {
        var age interface{} = 25
    
        fmt.Printf("原始接口变量的类型为 %T,值为 %v \n", age, age)
    
        t := reflect.TypeOf(age)
        v := reflect.ValueOf(age)
    
        // 从接口变量到反射对象
        fmt.Printf("从接口变量到反射对象:Type对象的类型为 %T \n", t)
        fmt.Printf("从接口变量到反射对象:Value对象的类型为 %T \n", v)
    
        // 从反射对象到接口变量
        i := v.Interface()
        fmt.Printf("从反射对象到接口变量:新对象的类型为 %T 值为 %v \n", i, i)
    
    }

    输出如下

    原始接口变量的类型为 int,值为 25 
    从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
    从接口变量到反射对象:Value对象的类型为 reflect.Value 
    从反射对象到接口变量:新对象的类型为 int 值为 25

    当然了,最后转换后的对象,静态类型为 interface{} ,如果要转成最初的原始类型,需要再类型断言转换一下,关于这点,我已经在上一节里讲解过了,你可以点此前往复习:()。

    i := v.Interface().(int)

    至此,我们已经学习了反射的两大定律,对这两个定律的理解,我画了一张图,你可以用下面这张图来加强理解,方便记忆。

    4.png

    第三定律

    To modify a reflection object, the value must be settable.

    反射世界是真实世界的一个『映射』,是我的一个描述,但这并不严格,因为并不是你在反射世界里所做的事情都会还原到真实世界里。

    第三定律引出了一个 settable (可设置性,或可写性)的概念。

    其实早在以前的文章中,我们就一直在说,Go 语言里的函数都是值传递,只要你传递的不是变量的指针,你在函数内部对变量的修改是不会影响到原始的变量的。

    回到反射上来,当你使用 reflect.Typeof 和 reflect.Valueof 的时候,如果传递的不是接口变量的指针,反射世界里的变量值始终将只是真实世界里的一个拷贝,你对该反射对象进行修改,并不能反映到真实世界里。

    因此在反射的规则里

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var name string = "Go编程时光"
    
        v := reflect.ValueOf(name)
        fmt.Println("可写性为:", v.CanSet())
    }

    输出如下

    可写性为: false

    要让反射对象具备可写性,需要注意两点

    完整代码如下

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var name string = "Go编程时光"
        v1 := reflect.ValueOf(&name)
        fmt.Println("v1 可写性为:", v1.CanSet())
    
        v2 := v1.Elem()
        fmt.Println("v2 可写性为:", v2.CanSet())
    }

    输出如下

    v1 可写性为: false
    v2 可写性为: true

    知道了如何使反射的世界里的对象具有可写性后,接下来是时候了解一下如何对修改更新它。

    反射对象,都会有如下几个以 Set 单词开头的方法

    5.png

    这些方法就是我们修改值的入口。

    来举个例子

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var name string = "Go编程时光"
        fmt.Println("真实世界里 name 的原始值为:", name)
    
        v1 := reflect.ValueOf(&name)
        v2 := v1.Elem()
    
        v2.SetString("Python编程时光")
        fmt.Println("通过反射对象进行更新后,真实世界里 name 变为:", name)
    }

    输出如下

    真实世界里 name 的原始值为: Go编程时光
    通过反射对象进行更新后,真实世界里 name 变为: Python编程时光

    【相关推荐:Go视频教程编程教学

    以上就是go语言中反射三定律是什么的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:go语言 Golang
    上一篇:go语言中return怎么用 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • go语言中for的写法有哪些• go语言中range关键字怎么用• go语言中append()怎么用• Go语言break停止语句有什么用• Go语言中注释有什么作用• go语言中return怎么用
    1/1

    PHP中文网