2018-05-02 Go Reflect

转载自: Go Reflect

最近在看一些go语言标准库以及第三方库的源码时,发现go的reflect被大量使用,虽然反射的机制大多数语言都支持,但好像都没有go一样这么依赖反射的特性。个人觉得,reflect使用如此频繁的一个重要原因离不开go的另一个特性,空接口interface{},reflect配合空接口,让原本是静态类型的go具备了很多动态类型语言的特征。 另外,虽然反射大大增加了go语言的灵活性,但要完全掌握它的原理和使用也还是有一点难度的。

go的reflect库有两个重要的类型:

reflect.Type
reflect.Value
Type,Value分别对应对象的类型和值数据

还有两个重要的函数:

reflect.TypeOf(i interface{}) Type
reflect.TypeOf()返回值的类型就是reflect.Type。

reflect.ValueOf(i interface{}) Value
reflect.ValueIOf()返回值的类型就是reflect.Value

reflect.Type
reflect.TypeOf(i interface{}) Type

因为reflect.Typeof的参数是空接口类型,因此可以接收任意类型的数据。 TypeOf()的返回值是这个接口类型对应的reflect.Type对象。通过Type提供的一些方法,就可以获得这个接口实际的静态类型。

import (
  "fmt"
  "reflect"
)

type Foo struct {
  X string
  Y int
}

func main() {
  var i int = 123
  var f float32 = 1.23
  var l []string = []string{"a", "b", "c"}

  fmt.Println(reflect.TypeOf(i))    //int
  fmt.Println(reflect.TypeOf(f))    //float32
  fmt.Println(reflect.TypeOf(l))    //[]string

  var foo Foo
  fmt.Println(reflect.TypeOf(foo))    //main.Foo

}

查看reflect包的源代码可以看到,reflect.Type的定义如下:

type Type interface {
  Align() int
  FieldAlign() int
  Method(int) Method
  MethodByName(string) (Method, bool)
  NumMethod() int
  Name() string
  String() string
  Elem() Type
  Field(i int) StructField
  FieldByName(name string) (StructField, bool)
  Len() int
  .....
}

可见reflect.Type是一个接口类型的对象,这个接口包含了很多方法,像Name(),Field(),Method()等,下面再通过实例来了解几个比较重要的方法。

type Foo struct {
  X string
  Y int
}

func (f Foo) do() {
  fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)

}

func main() {
  var s string = "abc"
  fmt.Println(reflect.TypeOf(s).String()) //string
  fmt.Println(reflect.TypeOf(s).Name())   //string

  var f Foo
  typ := reflect.TypeOf(f)
  fmt.Println(typ.String()) //main.Foo
  fmt.Println(typ.Name())     //Foo ,返回结构体的名字

}

上面的例子可见,通过Type.String(),Type.Name()方法就可以获得接口对应的静态类型。 下面几个方法,显示了Type的更多功能,特别是对于结构体对象而言。

上面的例子可见,通过Type.String(),Type.Name()方法就可以获得接口对应的静态类型。 下面几个方法,显示了Type的更多功能,特别是对于结构体对象而言。


Type的Field是一个StructFiled对象:

type StructField struct {
    Name    string
    PkgPath string

    Type      Type      // field type
    Tag       StructTag // field tag string
    Offset    uintptr   // offset within struct, in bytes
    Index     []int     // index sequence for Type.FieldByIndex
    Anonymous bool      // is an embedded field
}

Method相关的方法

var f Foo
typ := reflect.TypeOf(f)

fmt.Println(typ.NumMethod()) //1, Foo 方法的个数
m := typ.Method(0)
fmt.Println(m.Name) //do
fmt.Println(m.Type) //func(main.Foo)
fmt.Println(m.Func) //<func(main.Foo) Value>, 这个返回的是reflect.Value对象,后面再讲

Kind

Kind方法Type和Value都有,它返回的是对象的基本类型,例如int,bool,slice等,而不是静态类型。

var f = Foo{}
typ := reflect.TypeOf(f)
fmt.Println(typ)        //main.Foo
fmt.Println(typ.Kind()) //struct

var f2 = &Foo{}
typ2 := reflect.TypeOf(f2)
fmt.Println(typ2)        //*main.Foo
fmt.Println(typ2.Kind()) //ptr

kind()的返回值如下:

const (
  Invalid Kind = iota
  Bool
  Int
  Int8
  Int16
  Int32
  Int64
  Uint
  Uint8
  Uint16
  Uint32
  Uint64
  Uintptr
  Float32
  Float64
  Complex64
  Complex128
  Array
  Chan
  Func
  Interface
  Map
  Ptr
  Slice
  String
  Struct
  UnsafePointer
)

reflect.Value
reflect.ValueOf(i interface{}) Value

reflect.ValueOf()的返回值类型为reflect.Value,它实现了interface{}参数到reflect.Value的反射

type Foo struct {
  X string
  Y int
}

func (f Foo) do() {
  fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)

}


func main() {
  var i int = 123
  var f = Foo{"abc", 123}
  var s = "abc"
  fmt.Println(reflect.ValueOf(i)) //<int Value>
  fmt.Println(reflect.ValueOf(f)) //<main.Foo Value>
  fmt.Println(reflect.ValueOf(s)) //abc

  //Value.String()方法对string类型的数据做了特殊处理,会直接返回字符串的值。
  //其它类型对象返回的格式都是"<Type% Value>"

}

reflact.Value对象可以通过调用Interface()方法,再反射回interface{}对象

          reflect.ValueOf()                        Interface()
interface{} ---------------------> reflect.Value -------------------> interface{}

var i int = 123
fmt.Println(reflect.Valueof(i).Interface()) //123

var f = Foo{"abc", 123}
fmt.Println(f) //{abc 123}
fmt.Println(reflect.ValueOf(f).Interface() == f)  //true
fmt.Println(reflect.ValueOf(f).Interface())  //{abc 123}

Value的Field方法

和Type的Filed方法不一样,Type.Field()返回的是StructFiled对象,有Name,Type等属性,Value.Field()返回的还是一个Value对象。

var foo = Foo{"abc", 123}

val := reflect.ValueOf(foo)
fmt.Println(val.FieldByName("y")) //<int Value>  interface.Value对象

typ := reflect.Typeof(foo)
fmt.Println(typ.FieldByName("y")) //{  <nil>  0 [] false} false StructField对象
func main() {
  var f = Foo{"abc", 123}
  rv := reflect.ValueOf(f)
  rt := reflect.TypeOf(f)
  for i := 0; i < rv.NumField(); i++ {
      fv := rv.Field(i)
      ft := rt.Field(i)
      fmt.Printf("%s type is :%s ,value is %v\n", ft.Name, fv.Type(), fv.Interface())
  }
}

//X type is :string ,value is abc
//Y type is :int ,value is 123

设置Value的值

要设置reflect.Value的值还颇费周折,不能直接对Value进行赋值操作

var s = "abc"
fv := reflect.ValueOf(s)
fmt.Println(fv.CanSet()) //false
// fv.SetString("edf")   //panic

fv2 := reflect.ValueOf(&s)
fmt.Println(fv2.CanSet()) //false
// fv2.SetString("edf")      //panic

relect.Value是字符s的一个反射对象,是不能直接对它进行赋值操作的。 要对s进行赋值,需要先拿到s的指针对应的reflect.Value,然后通过Value.Elem()再对应到s,然后才能赋值操作。 这个地方是相当拗口啊:(

func main() {
  var i int = 123
  fv := reflect.ValueOf(i)
  fe := reflect.ValueOf(&i).Elem()  //必须是指针的Value才能调用Elem
  fmt.Println(fe)       //<int Value>
  fmt.Println(fv)       //<int Value>
  fmt.Println(fv == fe) //false

  fmt.Println(fe.CanSet()) //true
  fe.SetInt(456)
  fmt.Println(i) //456

}

Method

这个是reflect一个比较经典的使用场景,在知道对象方法名的情况下,调用对象的方法。

type Foo struct {
  X string
  Y int
}

func (f Foo) Do() {
  fmt.Printf("X is: %s, Y is: %d\n", f.X, f.Y)

}

func main() {
  var foo = &Foo{"abc", 123}
  reflect.ValueOf(foo).MethodByName("Do").Call([]reflect.Value{})

}

//方法名Do必须是大写的,否则会抛异常

reflect整体不是很好理解,如果要进一步掌握如何使用,以及在什么场景下用,建议看一些开源库的代码,来理解reflect的使用。参考:
web.go

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

推荐阅读更多精彩内容

  • 首先巴拉巴拉一下golang反射机制的三个定律 1.反射可以从接口类型到反射类型对象 2.反射可以从反射类型对象到...
    吃猫的鱼0阅读 2,891评论 0 1
  • Go语言做Web编程非常方便,并且在开发效率和程序运行效率方面都非常优秀。相比于Java,其最大的优势就是简便易用...
    暗黑破坏球嘿哈阅读 8,989评论 6 67
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 你有一个你喜欢的漫画家 有一个童话王国的梦 有一本自始至终都很爱的书 有一首听了多少遍也听不厌的歌 有一串心爱的手...
    黎小鹿阅读 206评论 0 0
  • 轮回得久了 忘自己本来是什么 做了什么 想做什么 那些旧事融化成风 刮过我边缘的叶子 我施以礼节性的旋转 旧事旧得...
    我是蝎大人阅读 304评论 2 3