golang反射机制介绍

golang反射机制介绍

Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法以及直接对他们的布局进行操作,这种机制称为反射(reflection)。

reflect.Type 和 reflect.Value

反射功能由reflect包提供,它定义了两个重要的类型:Type 和 Value,分别表示变量的类型和值。反射包里面,提供了reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的Type 和 Value:

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

可以看到,TypeOf和ValueOf的参数是interface{}类型,当把一个具体值赋给一个interface{}类型时,会发生一个隐式的类型转换,转换会生成一个包含两个部分内容的接口值:动态类型部分是操作数的类型,动态值部分是操作数的值。

reflect.TypeOf返回接口值对应的动态类型,注意返回的时具体值类型而不是接口类型。比如下面的输出的是"*os.File"而不是"io.Writer":

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(v)) // "*os.File"

同理,reflect.ValueOf返回接口动态值,以reflect.Value的形式返回,与reflect.TypeOf类似:

v := reflect.ValueOf(3)
fmt.Println(v) //"3"

调用Value的Type方法会把它的类型以reflect.Type方式返回:

t := v.Type() //an reflect.Type
fmt.Println(t.String()) //"int"

reflect.ValueOf的逆操作是reflect.Value.Interface方法,它返回一个interface{}接口值,与reflect.Value包含同一个具体值:

v := reflect.ValueOf(3)
x := v.Interface()
i := x.(int)
fmt.Printf("%d\n", i) //"3"

reflect.Value 与 interface{}都可以包含任意值。二者的区别是空接口(interface{})隐藏了值的布局信息、内置操作和相关方法,除非我们知道它的动态类型,并用一个类型断言来渗透进去,否则我们对所包含的值能做的事情很少。作为对比,Value有很多方法可以用来分析所包含的值,而不用知道它的类型。

利用反射遍历结构体

前面介绍的是利用反射打印简单数据类型的类型和结构,接下来介绍如何用反射遍历一个结构体,可以参考下面的一个代码:

func Display(i interface{}) {
    fmt.Printf("Display %T\n", i)   
    display("", reflect.ValueOf(i))
    
}
 
func display(path string, v reflect.Value) {
    switch v.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64,
        reflect.Uint, reflect.Uint8, reflect.Uint16,        
        reflect.Uint32, reflect.Uint64,         
        reflect.Float32, reflect.Float64,       
        reflect.Bool,       
        reflect.String:         
        fmt.Printf("%s: %v (%s)\n", path, v, v.Type().Name())       
    case reflect.Ptr:   
        v = v.Elem()        
        display(path, v)        
    case reflect.Slice, reflect.Array:  
        for i := 0; i < v.Len(); i++ {      
            display(" "+path+"["+strconv.Itoa(i)+"]", v.Index(i))           
        }       
    case reflect.Struct:    
        t := v.Type()   
        for i := 0; i < v.NumField(); i++ {         
            display(" "+path+"."+t.Field(i).Name, v.Field(i))           
        }       
    }   
}

首先创建一个Display函数,然后在该函数里面封装了一个display,display函数可以递归遍历结构体内部的字段,如果是简单的数据类型就直接打印出来,否则就递归遍历结构体成员。接下来简单解析一下display函数:

数组与切边:如果v为数组或者切边,则调用v.Len()函数,获取该数组或切边的长度,调用v.Index(i), 获取该数组或切边的第i个元素。

指针: 如果v为指针,调用v.Elem(),获取指针指向的变量,然后递归该变量。

结构体:如果v为结构体,v.NumField()将返回该结构体的字段数,v.Field(i)将返回第i个字段。

当然,switch语句这里,v.Kind()的类型不仅仅是这点,还有一些其他类型,比如reflect.Map,reflect.Interface, reflect.Func,reflect.Chan等等,这段代码的目的仅仅是为了展示如何用反射遍历结构体,所以没有必要考虑所有的结构,都是大同小异的。

接下来我们,我们定义一个内嵌结构体的结构体,并且调用Display函数,遍历该结构体:

type Person struct {
    Name   string   
    Age    int  
    Gender bool
    
}

type Student struct {
    Person *Person  
    Course []string 
    Core   []float32    
}

func main() {
    st := &Student{
        Person: &Person{    
            Name:   "Jim",          
            Age:    18,         
            Gender: true,           
        },  
        Course: []string{"Math", "Data Structe", "Algorithm"},      
        Core:   []float32{90.5, 85, 89.9},      
    }   
    Display(st) 
}

输出如下:

Display *main.Student
  .Person.Name: Jim (string)   
  .Person.Age: 18 (int)  
  .Person.Gender: true (bool)  
  .Course[0]: Math (string)   
  .Course[1]: Data Structe (string)   
  .Course[2]: Algorithm (string)  
  .Core[0]: 90.5 (float32)   
  .Core[1]: 85 (float32)   
  .Core[2]: 89.9 (float32)

利用reflect.Value修改值

Golang中,反射不只用来解析变量值,还可以改变变量的值,接下来介绍如何用reflect.Value来设置变量的值。

我们需要知道的是,在我们传参给reflect.ValueOf获取reflect.Value时,传入的是参数的拷贝,所以reflect.ValueOf获得的是传入参数的拷贝的reflect.Value,对于它的修改对原来的数据是没有影响的。所以如果我们想要修改原来的值,就需要用指针,然后还需要利用Elem方法获取指针指向的变量,通过修改Elem()返回的变量,才能真正的修改原始变量的值,如下:

x := 2
v := reflect.ValueOf(&x)
e := v.Elem()
e.Set(reflect.ValueOf(3))
fmt.Println(x) //"3"

这里,还有一些基本类型特化的Set变种:SetInt、SetUint、SetString、SetFloat等:

e.SetInt(4)
fmt.Println(e) //"4"

在更新变量前,我们可以用CanSet方法来判断是否可更改,如果更改一个不可更改的reflect.Value,将导致panic:

fmt.Println(e.CanSet()) //"true"
d := reflect.ValueOf(2)
fmt.Println(d.CanSet()) //"false"
d.Set(reflect.ValueOf(4)) // 这里将导致panic

备注

Golang的反射是i一个功能和表达能力都很强大的工具,但是使用它是要谨慎,具体有如下一些原因:

  • 基于反射的的代码都很脆弱。能导致编译器报告类型错误的每种写法,在反射中都有一个对应的的误用写法。编译器在编译时就能向你报告的这个错误,而反射错误则要等到执行时才以崩溃的方式来报告。并且反射还降低了自动重构和分析工具的安全性和准确度,因为它们无法检测到类型信息。

  • 我们要避免使用反射的原因是,反射的相关操作无法做静态类型检查,所以对于大量使用反射的代码是很难理解的。对于接受interfacef{}或者reflect.Value的函数,一定要写清楚期望的参数类型和其他限制条件

  • 基于反射的函数会比特定类型优化的函数慢一两个数量级。测试很适合用反射,但是对于关键路径上的函数,最好避免使用反射。

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

推荐阅读更多精彩内容

  • 第一次知道反射的时候还是许多年前在学校里玩 C# 的时候。那时总是弄不清楚这个复杂的玩意能有什么实际用途……然后发...
    勿以浮沙筑高台阅读 1,122评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,253评论 0 16
  • 。!!
    橘子殿下阅读 141评论 0 0
  • 小儿13个月了,能说能哼,能走能跳,身体健康,我想,这已是为人父母最高兴的事了吧。 昨儿清早,照例我和孩儿他爹7点...
    丹妹子在画画阅读 222评论 0 0