Go 反射机制

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循环,比如类型之类的
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • golang反射机制介绍 Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法...
    发仔很忙阅读 2,263评论 0 0
  • Go语言类型 Go语言是静态类型的编程语言,所有数据的类型在编译期确定了。 而且 Go 语言中即使是底层存的是一个...
    副班长国伟阅读 268评论 0 1
  • 各位学习Go语言的朋友,周末好,这次跟大家聊一聊Go语言的一个高级话题:反射。 这篇文章是从我过去的学习笔记修改来...
    大彬_一起学Golang阅读 1,368评论 2 21
  • 渐变的面目拼图要我怎么拼? 我是疲乏了还是投降了? 不是不允许自己坠落, 我没有滴水不进的保护膜。 就是害怕变得面...
    闷热当乘凉阅读 4,241评论 0 13
  • 夜莺2517阅读 127,718评论 1 9