go语言 反射机制

go语言 反射机制

1 反射是什么

1. 反射定律

反射可以将“接口类型变量”转换为“反射类型对象”。
反射可以将“反射类型对象”转换为“接口类型变量”。
如果要修改“反射类型对象”,其值必须是“可写的”。

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
10.2 反射的作用

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

典型应用是对象关系映射

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
}

var users []User
db.Find(&users)

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

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

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

3 反射的实现

Go的反射基础是接口和类型系统,Go的反射机制是通过接口来进行的。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

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再对空接口进行拆解,将接口类型变量转换为反射类型变量

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

定律2是定律1的反过程。

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。

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.如果要修改“反射类型对象”,其值必须是“可写的”。

运行一下程序:

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

出现panic了

panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x8e)
	/usr/local/go/src/reflect/value.go:259 +0x138
reflect.flag.mustBeAssignable(...)
	/usr/local/go/src/reflect/value.go:246
reflect.Value.SetFloat(0x488ec0, 0xc00001a0b8, 0x8e, 0x4018b851eb851eb8)
	/usr/local/go/src/reflect/value.go:1609 +0x37
main.main()
	/home/ricardo/error.go:8 +0xb3
exit status 2

因为反射对象v包含的是副本值,所以无法修改。

我们可以通过CanSet函数来判断反射对象是否可以修改,如下:

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

小结

1.反射对象包含了接口变量中存储的值以及类型。

2.如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值;

3.如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),则该反射对象不可以修改。

4 反射的实践

1.通过反射修改内容

var f float64 = 3.41
fmt.Println(f)
p := reflect.ValueOf(&f)
v := p.Elem()
v.SetFloat(6.18)
fmt.Println(f)
reflect.Elem() 

方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

2.通过反射调用方法
package main

import (
        "fmt"
        "reflect"
)

func hello() {
  fmt.Println("Hello world!")
}

func main() {
  hl := hello
  fv := reflect.ValueOf(hl)
  fv.Call(nil)
}

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

1.涉及到内存分配以及后续的垃圾回收

2.reflect实现里面有大量的枚举,也就是for循环,比如类型之类的

a. “接口类型变量”=>“反射类型对象”

所谓的反射类型,就是reflect.Type和reflect.Value

var a int = 30

v := reflect.ValueOf(a) //返回Value类型对象,值为30
t := reflect.TypeOf(a) //返回Type类型对象,值为int
fmt.Println(v)
fmt.Println(t)

v = reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址
t = reflect.TypeOf(&a) //返回Type类型对象,值为*int
fmt.Println(v)
fmt.Println(t)

上面的案例通过使用reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型对象value和type。

value中包含了接口中的实际值。
type中包含了接口中的实际类型。
大家可能对上面的案例感到疑惑,程序里没有接口类型变量啊,哪来的接口类型变量到反射类型对象的转换啊?
事实上,reflect.ValueOf和reflect.TypeOf的参数类型都是interface{},空接口类型,而返回值的类型是reflect.Value和reflect.Type,中间的转换由reflect包来实现。

b. “反射类型对象”=>“接口类型变量”

基本方法是: reflectValue值.Interface.(要转化为的类型)

根据一个reflect.Value类型(注意没有reflect.Type)的变量,我们可以使用 Interface方法恢复其接口类型的值。事实上,这个方法会把 type 和 value信息打包并填充到一个接口变量中,然后返回。

var a int = 30
value := reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址
t := value.Interface().(*int) //类型断言,断定v1中type=*int
fmt.Printf("%T %v\n", t, t) // *int 0xc420086008
fmt.Println(*t) // 30

cir := 6.28
value2 := reflect.ValueOf(cir)
t2 := value2.Interface().(float64)
fmt.Printf("%T %v\n", t2, t2) // float64 6.28
fmt.Println(t2) // 6.28

t3 := value2.Interface().(int) //error : interface conversion: interface {} is float64, not int
fmt.Println(t3)

最关键的两步

v1 := value.Interface()  // 返回的是一个接口变量
v2 := v1.(float64)       // 再判断这个接口变量是否能转化为某类型变量
c. 修改“反射类型对象”

基本方法: 指针类型的value.Elem().SetFloat(待赋的新值)

注意这里用到的也是reflect.ValueOf

var circle float64 = 6.28
value := reflect.ValueOf(circle)
fmt.Println(value.CanSet())           // false
fmt.Println(value)                    // 6.28

value2 := reflect.ValueOf(&circle)
fmt.Println(value2.CanSet())          // false
fmt.Println(value2)                   // 0xc420086008
 
 
value3 := reflect.ValueOf(&circle).Elem()
fmt.Println(value3.CanSet())          // true
fmt.Println(value3)                   // 6.28
 
 
value3.SetFloat(3.14)
fmt.Println(circle)                   // 3.14
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值