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提供的一些方法,就可以获得这个接口实际的静态类型。

 1import (
 2  "fmt"
 3  "reflect"
 4)
 5
 6type Foo struct {
 7  X string
 8  Y int
 9}
10
11func main() {
12  var i int = 123
13  var f float32 = 1.23
14  var l []string = []string{"a", "b", "c"}
15
16  fmt.Println(reflect.TypeOf(i))    //int
17  fmt.Println(reflect.TypeOf(f))    //float32
18  fmt.Println(reflect.TypeOf(l))    //[]string
19
20  var foo Foo
21  fmt.Println(reflect.TypeOf(foo))    //main.Foo
22
23}

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

 1type Type interface {
 2  Align() int
 3  FieldAlign() int
 4  Method(int) Method
 5  MethodByName(string) (Method, bool)
 6  NumMethod() int
 7  Name() string
 8  String() string
 9  Elem() Type
10  Field(i int) StructField
11  FieldByName(name string) (StructField, bool)
12  Len() int
13  .....
14}
15

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

 1type Foo struct {
 2  X string
 3  Y int
 4}
 5
 6func (f Foo) do() {
 7  fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)
 8
 9}
10
11func main() {
12  var s string = "abc"
13  fmt.Println(reflect.TypeOf(s).String()) //string
14  fmt.Println(reflect.TypeOf(s).Name())   //string
15
16  var f Foo
17  typ := reflect.TypeOf(f)
18  fmt.Println(typ.String()) //main.Foo
19  fmt.Println(typ.Name())     //Foo ,返回结构体的名字
20
21}

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

Field相关的方法
 1var f Foo
 2typ := reflect.TypeOf(f)
 3for i := 0; i < typ.NumField(); i++ {
 4  field := typ.Field(i)
 5  fmt.Printf("%s type is :%s\n", field.Name, field.Type)
 6}
 7
 8//x type is :string
 9//y type is :int
10
11field2, _ := typ.FieldByName("x") //等价于typ.Field(0),返回的也是StructField对象
12fmt.Println(field2.Name)          // x

Type的Field是一个StructFiled对象:

 1type StructField struct {
 2    Name    string
 3    PkgPath string
 4
 5    Type      Type      // field type
 6    Tag       StructTag // field tag string
 7    Offset    uintptr   // offset within struct, in bytes
 8    Index     []int     // index sequence for Type.FieldByIndex
 9    Anonymous bool      // is an embedded field
10}
Method相关的方法
1var f Foo
2typ := reflect.TypeOf(f)
3
4fmt.Println(typ.NumMethod()) //1, Foo 方法的个数
5m := typ.Method(0)
6fmt.Println(m.Name) //do
7fmt.Println(m.Type) //func(main.Foo)
8fmt.Println(m.Func) //<func(main.Foo) Value>, 这个返回的是reflect.Value对象,后面再讲
Kind

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

1var f = Foo{}
2typ := reflect.TypeOf(f)
3fmt.Println(typ)        //main.Foo
4fmt.Println(typ.Kind()) //struct
5
6var f2 = &Foo{}
7typ2 := reflect.TypeOf(f2)
8fmt.Println(typ2)        //*main.Foo
9fmt.Println(typ2.Kind()) //ptr

kind()的返回值如下:

 1const (
 2  Invalid Kind = iota
 3  Bool
 4  Int
 5  Int8
 6  Int16
 7  Int32
 8  Int64
 9  Uint
10  Uint8
11  Uint16
12  Uint32
13  Uint64
14  Uintptr
15  Float32
16  Float64
17  Complex64
18  Complex128
19  Array
20  Chan
21  Func
22  Interface
23  Map
24  Ptr
25  Slice
26  String
27  Struct
28  UnsafePointer
29)

reflect.Value

reflect.ValueOf(i interface{}) Value

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

 1type Foo struct {
 2  X string
 3  Y int
 4}
 5
 6func (f Foo) do() {
 7  fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)
 8
 9}
10
11
12func main() {
13  var i int = 123
14  var f = Foo{"abc", 123}
15  var s = "abc"
16  fmt.Println(reflect.ValueOf(i)) //<int Value>
17  fmt.Println(reflect.ValueOf(f)) //<main.Foo Value>
18  fmt.Println(reflect.ValueOf(s)) //abc
19
20  //Value.String()方法对string类型的数据做了特殊处理,会直接返回字符串的值。
21  //其它类型对象返回的格式都是"<Type% Value>"
22
23}

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

 1              reflect.ValueOf()                        Interface()
 2interface{} ---------------------> reflect.Value -------------------> interface{}
 3
 4var i int = 123
 5fmt.Println(reflect.Valueof(i).Interface()) //123
 6
 7var f = Foo{"abc", 123}
 8fmt.Println(f) //{abc 123}
 9fmt.Println(reflect.ValueOf(f).Interface() == f)  //true
10fmt.Println(reflect.ValueOf(f).Interface())  //{abc 123}
Value的Field方法

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

1var foo = Foo{"abc", 123}
2
3val := reflect.ValueOf(foo)
4fmt.Println(val.FieldByName("y")) //<int Value>  interface.Value对象
5
6typ := reflect.Typeof(foo)
7fmt.Println(typ.FieldByName("y")) //{  <nil>  0 [] false} false StructField对象
 1func main() {
 2  var f = Foo{"abc", 123}
 3  rv := reflect.ValueOf(f)
 4  rt := reflect.TypeOf(f)
 5  for i := 0; i < rv.NumField(); i++ {
 6      fv := rv.Field(i)
 7      ft := rt.Field(i)
 8      fmt.Printf("%s type is :%s ,value is %v\n", ft.Name, fv.Type(), fv.Interface())
 9  }
10}
11
12//X type is :string ,value is abc
13//Y type is :int ,value is 123
设置Value的值

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

1var s = "abc"
2fv := reflect.ValueOf(s)
3fmt.Println(fv.CanSet()) //false
4// fv.SetString("edf")   //panic
5
6fv2 := reflect.ValueOf(&s)
7fmt.Println(fv2.CanSet()) //false
8// fv2.SetString("edf")      //panic

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

 1func main() {
 2  var i int = 123
 3  fv := reflect.ValueOf(i)
 4  fe := reflect.ValueOf(&i).Elem()  //必须是指针的Value才能调用Elem
 5  fmt.Println(fe)       //<int Value>
 6  fmt.Println(fv)       //<int Value>
 7  fmt.Println(fv == fe) //false
 8
 9  fmt.Println(fe.CanSet()) //true
10  fe.SetInt(456)
11  fmt.Println(i) //456
12
13}
Method

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

 1type Foo struct {
 2  X string
 3  Y int
 4}
 5
 6func (f Foo) Do() {
 7  fmt.Printf("X is: %s, Y is: %d\n", f.X, f.Y)
 8
 9}
10
11func main() {
12  var foo = &Foo{"abc", 123}
13  reflect.ValueOf(foo).MethodByName("Do").Call([]reflect.Value{})
14
15}
16
17//方法名Do必须是大写的,否则会抛异常

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

本文分享自微信公众号 - Golang语言社区(Golangweb)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-06-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • go语言文件正则表达式搜索功能示例

    package main import ( "fmt" "os" "path/filepath" "regexp" ...

    李海彬
  • Golang 反射

    在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个Struct

    李海彬
  • 厚土Go学习笔记 | 14. switch 的条件写的有点灵活,不过风格还是go的一贯风格

    switch是很容易理解的,先来个代码,运行起来,看看你的操作系统是什么吧。 package main import ( "fmt" "run...

    李海彬
  • NVIDIA Jetson Xavier开发套件揭秘

    之前我们已经报道过NVIDIA推出新一代嵌入式高性能产品Jetson XavierNVIDIA发布新“掌中宝”开发套件:原来你是这样的Jetson Xavier...

    GPUS Lady
  • (四十五)golang--反射

    反射注意事项和使用细节: (1)reflect.Vale.Kind,获取变量的类别,返回的是一个常量;

    绝命生
  • [go语言]利用缓冲信道来实现网游帐号验证消息的分发和等待

    设想这样一个应用场景:一个网游登录服务器的实现里,每个玩家的连接用一个goroutine来处理,有一个主动对象AccountServer代表帐号服务器,Acco...

    李海彬
  • 嵌入式浏览器安全之网易云音乐RCE漏洞分析

    前面章节讲解了应用程序是如何与网页进行交互的,接下来章节分析通用软件历史漏洞,通过真实漏洞案例分析去了解嵌入式浏览器安全的攻击面。本章节讲的是网易云音乐rce漏...

    周俊辉
  • 嵌入式浏览器安全之网易云音乐RCE漏洞分析

    前面章节讲解了应用程序是如何与网页进行交互的,接下来章节分析通用软件历史漏洞,通过真实漏洞案例分析去了解嵌入式浏览器安全的攻击面。本章节讲的是网易云音乐rce漏...

    周俊辉
  • [go语言]利用缓冲信道来实现网游帐号验证消息的分发和等待

    设想这样一个应用场景:一个网游登录服务器的实现里,每个玩家的连接用一个goroutine来处理,有一个主动对象AccountServer代表帐号服务器,Acco...

    李海彬
  • 利用缓冲信道来实现网游帐号验证消息的分发和等待

    设想这样一个应用场景:一个网游登录服务器的实现里,每个玩家的连接用一个goroutine来处理,有一个主动对象AccountServer代表帐号服务器,Acco...

    李海彬

扫码关注云+社区

领取腾讯云代金券