美文网首页
Go 反射机制

Go 反射机制

作者: 泥人冷风 | 来源:发表于2020-12-23 17:32 被阅读0次

    1 反射是什么

    反射的概念是由Smith在1982年首次提出的,主要是程序可以访问、检测和修改它本身状态或行为的一种能力。
    Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

    2 反射的作用

    2.1 在编写不定传参类型函数的时候,或传入类型过多时

    典型应用是对象关系映射

    package main
    
    import (
        "database/sql"
        "time"
    
        "gorm.io/driver/sqlite"
        "gorm.io/gorm"
    )
    
    type User struct {
        gorm.Model
        Name         string
        Age          sql.NullInt64
        Birthday     *time.Time
        Email        string  `gorm:"type:varchar(100);unique_index"`
        Role         string  `gorm:"size:255"`        // set field size to 255
        MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
        Num          int     `gorm:"AUTO_INCREMENT"`  // set num to auto incrementable
        Address      string  `gorm:"index:addr"`      // create index with name `addr` for address
        IgnoreMe     int     `gorm:"_"`               // ignore this field
    }
    
    func main() {
        var users []User
        db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
        db.Find(&users)
    }
    
    

    2.2 不确定调用哪个函数,需要根据某些条件来动态执行

    func bridge(funcPtr interface{}, args ...interface{})
    

    第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数。

    3 反射的实现

    Go的反射基础是接口和类型系统,Go的反射机制是通过接口来进行的。
    Go语言在reflect包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

    3.1 反射三定律

    3.1.1 反射可以将“接口类型变量”转换为“反射类型对象”。

    反射提供一种机制,允许程序在运行时访问接口内的数据。首先介绍一下reflect包里的两个方法reflect.Value和reflect.Type

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var Num float64 = 3.14
    
        v := reflect.ValueOf(Num)
        t := reflect.TypeOf(Num)
    
        fmt.Println("Reflect : Num.Value = ", v)
        fmt.Println("Reflect : Num.Type  = ", t)
    }
    

    执行结果

    Reflect : Num.Value =  3.14
    Reflect : Num.Type  =  float64
    

    上面的例子通过reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型对象v和t,v是Num的值,t也是Num的类型。
    先来看一下reflect.ValueOf和reflect.TypeOf的函数签名

    func TypeOf(i interface{}) Type
    
    func (v Value) interface() (i interface{})
    

    两个方法的参数类型都是空接口
    在整个过程中,当我们调用reflect.TypeOf(x)的时候,
    当我们调用reflect.TypeOf(x)的时候,Num会被存储在这个空接口中,然后reflect.TypeOf再对空接口拆解,将接口类型变量转换为反射类型变量

    3.1.2 反射可以将“反射类型对象”转换为“接口类型变量”

    定律2是定律1的反过程

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var Num = 3.14
        v := reflect.ValueOf(Num)
        t := reflect.TypeOf(Num)
        fmt.Println(v)
        fmt.Println(t)
    
        origin := v.Interface().(float64)
        fmt.Println(origin)
    }
    

    执行结果

    3.14
    float64
    3.14
    

    3.1.3 如果要修改“反射类型对象”,其值必须是“可写的”

    package main
    
    import (
        "reflect"
    )
    
    func main() {
        var Num float64 = 3.14
        v := reflect.ValueOf(Num)
        v.SetFloat(6.18)
    }
    

    执行结果

    panic: reflect: reflect.Value.SetFloat using unaddressable value
    
    goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x8e) c:/go/src/reflect/value.go:260 +0x146 reflect.flag.mustBeAssignable(...) c:/go/src/reflect/value.go:247 reflect.Value.SetFloat(0xc7bb20, 0xc00009c000, 0x8e, 0x4018b851eb851eb8) c:/go/src/reflect/value.go:1619 +0x3e main.main() c:/Users/learn/go/GoHello/main.go:10 +0xba
    
    Process exiting with code: 2 signal: false
    
    

    因为反射对象v包含的是副本值,所以无法修改。
    我们可以通过CanSet函数来判断反射对象是否可以修改,如下:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var Num float64 = 3.14
        v := reflect.ValueOf(Num)
        fmt.Println("v的可写性:", v.CanSet())
    }
    

    执行结果

    v的可写性: false
    

    4 反射的实践

    4.1 通过反射修改内容

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var f float64 = 3.41
        fmt.Println(f)
        p := reflect.ValueOf(&f)
        v := p.Elem()
        v.SetFloat(6.18)
        fmt.Println(f)
    
    }
    

    执行结果

    3.41
    6.18
    

    4.2 通过反射调用方法

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func hello() {
        fmt.Println("Hello world!")
    }
    
    func main() {
        hl := hello
        fv := reflect.ValueOf(hl)
        fv.Call(nil)
    }
    

    执行结果

    Hello world!
    

    反射会使得代码执行效率较慢,原因有

    • 1 涉及到内存分配以及后续的垃圾回收
    • 2 reflect实现里面有大量的枚举,也就是for循环,比如类型之类的

    相关文章

      网友评论

          本文标题:Go 反射机制

          本文链接:https://www.haomeiwen.com/subject/scwynktx.html