go(golang)语言的反射实现

什么是反射

在 Go 语言中,反射(Reflection)是指在程序运行时动态地获取变量的类型信息和值,并对其进行操作的能力。通过反射,我们可以在不知道具体类型的情况下,对变量进行类型检查、调用方法、获取和设置字段值等操作。

反射的实现

反射

go 语言的反射是通过接口实现的。而 go 中的接口变量其实是用 iface 和 eface 这两个结构体来表示的:
iface 表示某一个具体的接口(含有方法的接口)
eface 表示一个空接口(interface{})
go底层的类型信息是使用 _type 结构体来存储的。

type iface struct {
    tab  *itab // 方法表
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}
interfacetype的mhdr存放函数,itab._type存放类型
一个接口中包含了变量的类型信息和类型的数据。因此,我们才可以通过反射来获取到变量的类型信息,以及变量的数据信息。

在包 reflect 中实现了运行时的反射能力,反射包中有两对非常重要的函数和类型,它们与函数是一一对应的关系。

  • 两个函数
    reflect.TypeOf 能获取类型信息
    reflect.ValueOf 能获取数据的运行时表示

  • 两个类型
    reflect.Type 和 reflect.Value,reflect.Type 是一个接口而 reflect.Value 是一个结构体。

type Type interface {
    // Methods applicable to all types.

    Method(int) Method

    MethodByName(string) (Method, bool)

    NumMethod() int

    Name() string

    Kind() Kind

    // Size returns the number of bytes needed to store
    // a value of the given type; it is analogous to unsafe.Sizeof.
    Size() uintptr

    ……
    ……
}

type Value struct {
    //Value的类型
    typ *rtype
    ptr unsafe.Pointer
    flag
}

三大法则

  • 第一法则(从 interface{} 变量可以反射出反射对象)
    基本类型 int 会转换成 interface{} 类型,这也就是为什么第一条法则是从接口到反射对象。

  • 第二法则(从反射对象可以获取 interface{} 变量)
    将反射对象还原成接口类型的变量,采用 reflect.Value.Interface,注意需要显示转换

v := reflect.ValueOf(1)
v.Interface().(int)
  • 第三法则(要修改反射对象,其值必须可设置)
func main() {
    i := 1
    v := reflect.ValueOf(&i)
    v.Elem().SetInt(10)
    fmt.Println(i)
}

调用 reflect.ValueOf 获取变量指针;
调用 reflect.Value.Elem 获取指针指向的变量;
调用 reflect.Value.SetInt 更新变量的值:

使用场景

  • 处理未知类型的数据
  • 实现通用的数据处理逻辑
  • 实现序列化和反序列化

注意

反射的使用会带来一定的性能损耗,因此在性能要求较高的场景下,应慎重使用反射。同时,反射的代码可读性较低,容易引入错误,因此应尽量避免滥用反射。

方法使用

判定及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

反射值对象的判定及获取元素的方法

方法名 备注 返回值
Elem() 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value Value
Addr() 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 Value
CanAddr() 表示值是否可寻址 bool
CanSet() 返回值能否被修改。要求值可寻址且是导出的字段 bool

使用 reflect.Value 修改值的相关方法如下表所示。

反射值对象修改值的方法

方法名 备注
Set(x Value) 将值设置为传入的反射值对象的值。
SetInt(x int64) 使用 int64 设置值。当值的类型不是 int、int8、int16、int32、int64 时会发生宕机。
SetUint(x uint64) 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机。
SetFloat(x float64) 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机。
SetBool(x bool) 使用 bool 设置值。当值的类型不是 bool 时会发生宕机。
SetBytes(x []byte) 设置字节数组 []byte 值。当值的类型不是 []byte 时会发生宕机。
SetString(x string) 设置字符串值。当值的类型不是 string 时会发生宕机。

基本使用

package main

import (
    "fmt"
    "reflect"
)

func main() {

    var a int = 3

    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)

    // 根据反射类型对象创建类型实例
    aIns := reflect.New(typeOfA)

    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}

通过反射调用函数

type node struct {
    name string
    val  string
}

func (t node) Print() {
    fmt.Printf("output node val is %s\n", t.val)
}

func main() {
    var obj = node{
        name: "根",
        val:  "1",
    }
    typ := reflect.TypeOf(obj)
    fmt.Println(typ.Kind())

    va := reflect.ValueOf(obj)

    method := va.MethodByName("Print") //注意此处要大写开头才能导出字段
    // 判断方法是否存在
    if method.IsValid() {
        // 调用方法,传入空参数
        method.Call(nil)
    }
}

引用

http://c.biancheng.net/view/116.html

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

推荐阅读更多精彩内容