golang之反射

Posted traditional

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang之反射相关的知识,希望对你有一定的参考价值。

什么是反射?

反射:用大白话解释就是,程序在运行期间可以动态地查看某个变量的值的类型、并且还能够动态调用、修改自身的行为。python应该是反射机制最为彪悍的语言了,当然查看自身类型更是不在话下,这一点动态语言显然占据绝对的优势。而golang虽然作为静态语言,但也是支持反射的,主要通过reflect包实现,并且功能还很强大。

为什么要有反射?

有以下两个场景:

  • 当一个函数接收不同类型的参数要表现不同的行为时。
  • 根据外界返回的值的类型不同,表现的行为也不同。

举个栗子:

package main

import "fmt"

func foo(a interface{}){
    //这里使用断言,先不使用反射
    switch t:=a.(type) {
    case int:
        fmt.Printf("t是整型,值为%d
", t)
    case string:
        fmt.Printf("t是字符串,值为%s
", t)
    case float64:
        fmt.Printf("t是浮点型,值为%f
", t)
    default:
        fmt.Println("我也不知道什么类型")
    }
}


func main() {
    var v interface{} = "abc"
    foo(v)  // t是字符串,值为abc
}

目前我们是通过类型断言来实现的,后面使用反射也是类似的。反射很强大,但是不建议乱用。

  • 反射相关的代码比较难以阅读,代码的可读性也是我们需要考虑的
  • 反射的过程如果出错了,那么不好意思,只会在运行时才会报错,在编译的时候是检测不出来的。这就可能导致,你的服务都已经完美运行一个月了,但是有一天却突然挂了。如果出现这种情况,那么你要好好想想是不是你的反射相关的代码出问题了。
  • 反射是动态的检测变量的值的类型,这个是很损失效率的,如果没必要,那么尽量不要使用反射

reflect.Type和reflect.Value

golang中描述一个变量有两种方式,一个是reflect.Type(通过reflect.TypeOf(xxx)获取),另一个则是reflect.Value(通过reflect.ValueOf(xxx)获取)。Type和Value分别是从一个变量的类型和值来阐述其特征。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    var b = "satori"
    var c = 3.14
    var d = [3]int{1, 2, 3}
    var e = []int{1, 2, 3}
    var f = map[int]int{1: 2, 2: 3}
    var g = struct {
        name string
        age int
    }{name: "mashiro", age: 17}
    var h = new(int)
    var i = make(chan int)

    fmt.Println(reflect.TypeOf(a), " --- ", reflect.ValueOf(a)) //int  ---  123
    fmt.Println(reflect.TypeOf(b), " --- ", reflect.ValueOf(b)) //string  ---  satori
    fmt.Println(reflect.TypeOf(c), " --- ", reflect.ValueOf(c)) //float64  ---  3.14
    fmt.Println(reflect.TypeOf(d), " --- ", reflect.ValueOf(d)) //[3]int  ---  [1 2 3]
    fmt.Println(reflect.TypeOf(e), " --- ", reflect.ValueOf(e)) //[]int  ---  [1 2 3]
    fmt.Println(reflect.TypeOf(f), " --- ", reflect.ValueOf(f)) //map[int]int  ---  map[1:2 2:3]
    //匿名结构体会直接返回定义的结构体,如果有名字直接返回名字
    fmt.Println(reflect.TypeOf(g), " --- ", reflect.ValueOf(g)) //struct { name string; age int }  ---  {mashiro 17}
    fmt.Println(reflect.TypeOf(h), " --- ", reflect.ValueOf(h)) //*int  ---  0xc000062080
    fmt.Println(reflect.TypeOf(i), " --- ", reflect.ValueOf(i)) //chan int  ---  0xc00004c060
}

我们看到对于TypeOf函数来说,你的变量定义的时候是什么类型,那么返回的就是什么类型。只不过此时返回的是一个Type,是一个结构体,你不能直接和字符串去比较,我们可以调用string方法转换成字符串。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    fmt.Println(reflect.TypeOf(a).String()) //int
    fmt.Println(reflect.TypeOf(a).String()[0: 2]) //in
}

而对于ValueOf函数来说,返回的就是你的这个变量的值。但是需要注意的是,这个值是一个Value类型,也是一个结构体,想使用的话是需要转化的。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    var b = "satori"
    var c = 3.14
    var d = [3]int{1, 2, 3}
    var e = []int{1, 2, 3}
    var f = map[int]int{1: 2, 2: 3}
    var g = struct {
        name string
        age int
    }{name: "mashiro", age: 17}
    var h = new(int)
    var i = make(chan int)

    //这里直接调用了Int(),是因为我们知道这里返回的是int。如果你能确定值的类型,那么你可以直接转化
    fmt.Println(reflect.ValueOf(a).Int()) //123
    //比如这里的b我知道它就是一个string,那么我就直接通过调用String()方法来转了
    fmt.Println(reflect.ValueOf(b).String()) //satori

    //假设变量c我们不确定类型,那么可以通过如下方法。先转换成interface{},然后采用类型断言的方式就可以了
    switch t:= reflect.ValueOf(c).Interface().(type){
    case int:
        fmt.Println("int", t)
    case float64:
        fmt.Println("float", t) //float 3.14
    }

    //而d是一个数组,这种类型,我们没办法直接调用函数转化了。这个时候只能通过interface{}的方式。同理切片、map等等也是如此
    fmt.Println(reflect.ValueOf(d).Interface().([3]int)) //[1 2 3]
    fmt.Println(reflect.ValueOf(e).Interface().([]int)) //[1 2 3]
    fmt.Println(reflect.ValueOf(f).Interface().(map[int]int)) //map[1:2 2:3]
    //这里必须写上字段名,而且不能写错,否则也会断言失败。总而言之,你定义的时候写的什么类型,就是什么类型
    fmt.Println(reflect.ValueOf(g).Interface().(struct{name string; age int})) //{mashiro 17}
    fmt.Println(reflect.ValueOf(h).Interface().(*int)) //0xc000062080
    fmt.Println(reflect.ValueOf(i).Interface().(chan int)) //0xc00004c060
}

我们可以看一下,Type在底层的定义。

type Type interface {
    //支持很多方法,但是不是每一个都常用
    
    //此类型的变量对齐后所占用的字节数
    Align() int
    //struct 的字段对齐后占用的字节数
    FieldAlign() int
    //返回类型方法集里的第 `i` (传入的参数)个方法
    Method(int) Method
    // 通过名称获取方法
    MethodByName(string) (Method, bool)
    // 获取类型方法集里导出的方法个数
    NumMethod() int
    // 类型名称
    Name() string
    // 返回类型所在的路径,如:encoding/base64
    PkgPath() string
    // 返回类型的大小,和 unsafe.Sizeof 功能类似
    Size() uintptr
    //返回类型的字符串表示形式
    String() string
    //返回类型的类型值
    Kind() Kind
    // 类型是否实现了接口 u
    Implements(u Type) bool
    // 是否可以赋值给 u
    AssignableTo(u Type) bool
    // 是否可以类型转换成 u
    ConvertibleTo(u Type) bool
    // 类型是否可以比较
    Comparable() bool
    
    // 下面这些函数只有特定类型可以调用
    // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
    
    // 类型所占据的位数
    Bits() int
    // 返回通道的方向,只能是 chan 类型调用
    ChanDir() ChanDir
    // 返回类型是否是可变参数,只能是 func 类型调用
    // 比如 t 是类型 func(x int, y ... float64)
    // 那么 t.IsVariadic() == true
    IsVariadic() bool
    // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
    Elem() Type
    // 返回结构体类型的第 i 个字段,只能是结构体类型调用
    // 如果 i 超过了总字段数,就会 panic
    Field(i int) StructField
    // 返回嵌套的结构体的字段
    FieldByIndex(index []int) StructField
    // 通过字段名称获取字段
    FieldByName(name string) (StructField, bool)
    // FieldByNameFunc returns the struct field with a name
    // 返回名称符合 func 函数的字段
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 获取函数类型的第 i 个参数的类型
    In(i int) Type
    // 返回 map 的 key 类型,只能由类型 map 调用
    Key() Type
    //返回 Array 的长度,只能由类型 Array 调用
    Len() int
    //返回类型字段的数量,只能由类型 Struct 调用
    NumField() int
    //返回函数类型的输入参数个数
    NumIn() int
    //返回函数类型的返回值个数
    NumOut() int
    //返回函数类型的第 i 个值的类型
    Out(i int) Type
    //返回类型结构体的相同部分
    common() *rtype
    //返回类型结构体的不同部分
    uncommon() *uncommonType
}

至于Value,这个结构体里面的成员都是未导出的,关键它绑定了很多的方法,比如我们上面说的:Int(),String(),Interface()等等,也包括Type里面的很多方法。

Type和Kind

另外各位是不是发现虽然我们定义的是什么类型,Type在形式上也是什么类型这种形式虽然保持了一致性,但是不是也有点不好呢?比如说,我们想判断一个函数返回的到底是一个数组还是结构体(假设有一个这样的需求),但是我们知道数组个数不同、结构体字段个数不同、类型不同、对了还有名字不同,都会返回不同的结果,难道这些我们在断言的时候都要写一遍吗?数组可能有100个元素,难道光数组我们就要写100个case语句?显然我们不希望这样的结果,我们希望不管数组元素多少个,只要是一个数组,那么返回一个array就好了,如果是一个结构体,那么你返回struct就行。同理指针也是,我不在乎你到底是什么类型的指针,只要是指针,那么你就返回一个pointer,告诉我你是一个指针就行。那么能不能实现呢?显然可以,从变量的类型来描述在golang中除了Type之外还有一个Kind。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    var b = "satori"
    var c = 3.14
    var d = [3]int{1, 2, 3}
    var e = []int{1, 2, 3}
    var f = map[int]int{1: 2, 2: 3}
    var g = struct {
        name string
        age int
    }{name: "mashiro", age: 17}
    var h = new(int)
    var i = make(chan int)

    //这个Type是一个结构体,调用其Kind方法就能拿到我们想要的结果
    fmt.Println(reflect.TypeOf(a), "---", reflect.TypeOf(a).Kind())  //int --- int
    fmt.Println(reflect.TypeOf(b), "---", reflect.TypeOf(b).Kind())  //string --- string
    fmt.Println(reflect.TypeOf(c), "---", reflect.TypeOf(c).Kind())  //float64 --- float64
    fmt.Println(reflect.TypeOf(d), "---", reflect.TypeOf(d).Kind())  //[3]int --- array
    fmt.Println(reflect.TypeOf(e), "---", reflect.TypeOf(e).Kind())  //[]int --- slice
    fmt.Println(reflect.TypeOf(f), "---", reflect.TypeOf(f).Kind())  //map[int]int --- map
    fmt.Println(reflect.TypeOf(g), "---", reflect.TypeOf(g).Kind())  //struct { name string; age int } --- struct
    fmt.Println(reflect.TypeOf(h), "---", reflect.TypeOf(h).Kind())  //*int --- ptr
    fmt.Println(reflect.TypeOf(i), "---", reflect.TypeOf(i).Kind())  //chan int --- chan
} 

我们看到,除了int、string、float之外,其他的都变化了。像数组返回的是array、切片返回的是slice、map返回的是map、结构体直接返回一个struct、指针直接返回一个ptr、channel直接返回一个chan。但是注意:这些返回的是一个Kind类型,我们还是可以调用String方法转成字符串进行比较,当然也可以直接比较。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    var b = "satori"
    var c = 3.14
    var d = [3]int{1, 2, 3}
    var e = []int{1, 2, 3}
    var f = map[int]int{1: 2, 2: 3}
    var g = struct {
        name string
        age int
    }{name: "mashiro", age: 17}
    var h = new(int)
    var i = make(chan int)

    //调用String()得到字符串
    fmt.Println(reflect.TypeOf(a).Kind().String() == "int")  //true
    //也可以直接比较,使用reflect,里面有很多类型
    fmt.Println(reflect.TypeOf(b).Kind() == reflect.String)  //true
    fmt.Println(reflect.TypeOf(c).Kind() == reflect.Float64)  //true
    fmt.Println(reflect.TypeOf(d).Kind() == reflect.Array)  //true
    fmt.Println(reflect.TypeOf(e).Kind() == reflect.Slice)  //true
    fmt.Println(reflect.TypeOf(f).Kind() == reflect.Map)  //true
    fmt.Println(reflect.TypeOf(g).Kind() == reflect.Struct)  //true
    fmt.Println(reflect.TypeOf(h).Kind() == reflect.Ptr)  //true
    fmt.Println(reflect.TypeOf(i).Kind() == reflect.Chan)  //true
}

而且这个Kind()方法除了reflect.Type可以调用之外,reflect.Value也是可以调用的。并且reflect.Value调用Type方法,还能够得到reflect.Type。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    // reflect.TypeOf(a)返回一个Type,但是通过 reflect.ValueOf(a)得到一个Value,然后再手动调用Type()方法得到Type也是可以的
    fmt.Println(reflect.TypeOf(a) == reflect.ValueOf(a).Type())  //true
    //得到Kind,除了对Type调用Kind()方法之外,还可以通过对Value调用Kind()方法获取。
    fmt.Println(reflect.TypeOf(a).Kind() == reflect.ValueOf(a).Kind())  //true
    //同理,故意兜个圈子也行
    fmt.Println(reflect.TypeOf(a).Kind() == reflect.ValueOf(a).Type().Kind())  //true
}

一张图来表示Type、Value、Kind三者的关系。

技术图片

通过反射修改原对象

如果通过反射来修改变量,那么反射变量就必须是可设置的。怎么理解呢?我们通过修改反射变量,会影响原变量,就称之为反射变量是可设置的。

package main

import (
    "fmt"
    "reflect"
)


func main() {
    var a = 123
    //此时的v就是反射变量,如果这里我们传递了a,而不是a的指针,那么调用Elem()方法是会报错的
    //因为golang是值传递,所以获取了反射变量也不会影响原来的变量,因此这里必须要传递指针
    //传递了还不够,还必须要调用Elem方法,此时的v才能真正的代表a
    v := reflect.ValueOf(&a).Elem()
    //调用CanSet,表示该反射变量是否是可设置的
    fmt.Println(v.CanSet())  //true

    //设置值
    v.SetInt(456)
    //此时的a已经变了
    //总之就如同函数传参一样,如果想要修改原变量,那么反射变量必须获得原变量的地址才行
    fmt.Println(a)  //456
}
package main

import (
    "fmt"
    "reflect"
)

type girl struct {
    name string
    age int `json:"年龄"`
}


func main() {
    var g = girl{"mashiro", 17}
    //如果是为了拿到值的话,那么使用reflect.ValueOf(&g).Elem()
    //这里查看字段属性使用了reflect.TypeOf(&g).Elem()
    //另外reflect.TypeOf(&g).Elem()等价于reflect.ValueOf(&g).Type().Elem()
    reflect_g := reflect.TypeOf(&g).Elem()
    //通过字段名查找相应字段
    s, _ := reflect_g.FieldByName("age")
    fmt.Println(s.Tag.Get("json"))  //年龄
}

反射获取结构体的属性

package main

import (
    "fmt"
    "reflect"
)

type girl struct {
    name string
    age int
    gender string
}
func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}

func main() {
    g := girl{"mashiro", 17, "f"}
    //查看有多少个字段,以下几种方式是一样的
    t1, t2 := reflect.TypeOf(g), reflect.TypeOf(&g).Elem()
    v1, v2 := reflect.ValueOf(g), reflect.ValueOf(&g).Elem()
    fmt.Println(t1.NumField(), t2.NumField()) //3 3
    fmt.Println(v1.NumField(), v2.NumField()) //3 3

    //查看这个结构体绑定了多少个方法,我们看到指针接收者是没有计算在内的
    fmt.Println(t1.NumMethod(), t2.NumMethod()) //3 3
    fmt.Println(v1.NumMethod(), v2.NumMethod()) //3 3
}
//可能有人搞不清,针对于结构体到底什么时候用reflect.TypeOf,什么时候用reflect.ValueOf
//我想说的是,对于Type和Value来说,它们有很多方法都是相同的,比如这里的查看字段个数、方法个数。
//当然还有很多名字相同,但是作用不同的方法,比如FieldByName、FieldByIndex等等
//以及调用Field(i)获取第几个字段
//虽然方法名一样,但是Type调用返回的是一个StructField,用于描述结构体对应字段的信息的,比如字段名等等
//而Value调用返回的是一个Value类型,是用来获取结构体对应字段的值的。

反射获取结构体字段的属性

package main

import (
    "fmt"
    "reflect"
)

type girl struct {
    Name string
    Age int
    Gender string
}

func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}


func main() {
    g := girl{"mashiro", 17, "f"}
    ref := reflect.TypeOf(g)
    for i:=0;i<ref.NumField();i++{
        //Field表示获取指定字段,i表示索引
        //这里获取的t是一个StructField,包含了字段的信息
        t := ref.Field(i)
        fmt.Println(t.Name)
        /*
        Name
        Age
        Gender
         */
    }

    val := reflect.ValueOf(g)
    for i:=0;i<val.NumField();i++{
        //Field作用同上,但是这个是Value调用Field
        //返回的不再是StructField,而是一个Value,我们可以直接拿到值
        t := val.Field(i)
        fmt.Println(t.Interface())
        /*
        mashiro
        17
        f
        */
    }
    
}
package main

import (
    "fmt"
    "reflect"
)

type girl struct {
    Name string
    Age int
    Gender string
}

func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}


func main() {
    g := girl{"mashiro", 17, "f"}
    ref := reflect.TypeOf(g)
    //当然也可以针对于方法
    for i:=0;i<ref.NumMethod();i++{
        t := ref.Method(i)
        fmt.Println(t.Name)
        /*
        Foo1
        Foo2
        Foo4
         */
    }

    val := reflect.ValueOf(g)
    for i:=0;i<val.NumMethod();i++{
        t := val.Method(i)
        //t.Call调用函数,里面接收一个[]Value,返回一个[]Value
        t.Call([]reflect.Value{})
        /*
        Foo1
        Foo2
        Foo4
        */
    }

}

反射修改结构体的字段的值

当我们想修改结构体指定字段的值,用反射该如何实现呢?

package main

import (
    "fmt"
    "reflect"
)

type girl struct {
    Name string
    Age int
    Gender string
}

func main() {
    g := girl{"mashiro", 17, "f"}
    //修改值的话,肯定要用ValueOf。因为TypeOf是用来获取字段本身属性的
    v := reflect.ValueOf(&g).Elem()
    //是可以设置的
    fmt.Println(v.FieldByName("Age").CanSet()) //true
    //设置值
    v.FieldByName("Name").SetString("satori")
    //打印发现已经被修改了
    fmt.Println(g)
}

以上是关于golang之反射的主要内容,如果未能解决你的问题,请参考以下文章

golang之反射

一看就懂系列之Golang的反射

golang碎片整理之反射

golang/go语言Go语言之反射

golang goroutine例子[golang并发代码片段]

[golang]反射的用处--代码自动生成