美文网首页Golang程序员go语言学习
Golang 学习笔记(11)—— 反射

Golang 学习笔记(11)—— 反射

作者: ChainZhang | 来源:发表于2018-01-05 17:00 被阅读235次

    转载请注明出处:Golang 学习笔记(11)—— 反射

    Golang

    介绍

    反射是程序执行时检查其所拥有的结构。尤其是类型的一种能力。这是元编程的一种形式。它同一时候也是造成混淆的重要来源。

    每一个语言的反射模型都不同(同一时候很多语言根本不支持反射)。本节将试图明白解释在 Go 中的反射是怎样工作的

    实现

    package

    import "reflect"
    

    Type和Value

    首先,reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。

    Type就是定义的类型的一个数据类型,Value是值的类型

    下面分别对Type和Value做个简单介绍一下

    Type

    Type类型用来表示一个go类型。

    不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。

    获取Type对象的方法:

    func TypeOf(i interface{}) Type
    

    例如:

    str := "this is string"
    type := reflect.TypeOf(str)
    

    Type中常用的方法:

    • Kind() Kind
      Kind返回该接口的具体分类
      Kind代表Type类型值表示的具体分类。零值表示非法分类。
    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
    )
    
    • Name() string
      Name返回该类型在自身包内的类型名,如果是未命名类型会返回""

    • PkgPath() string
      PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
      如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""

    • NumField() int
      返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic

    • Field(i int) StructField
      返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic

    • FieldByIndex(index []int) StructField
      返回索引序列指定的嵌套字段的类型,
      等价于用索引中每个值链式调用本方法,如非结构体将会panic

    • FieldByName(name string) (StructField, bool)
      返回该类型名为name的字段(会查找匿名字段及其子字段),
      布尔值说明是否找到,如非结构体将panic

    • type StructField

    type StructField struct {
        // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
        Name    string
        PkgPath string
        Type      Type      // 字段的类型
        Tag       StructTag // 字段的标签
        Offset    uintptr   // 字段在结构体中的字节偏移量
        Index     []int     // 用于Type.FieldByIndex时的索引切片
        Anonymous bool      // 是否匿名字段
    }
    

    StructField类型描述结构体中的一个字段的信息。

    • NumMethod() int
      返回该类型的方法集中方法的数目
      匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
      匿名字段导致的歧义方法会滤除

    • Method(int) Method
      返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
      对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
      对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

    • MethodByName(string) (Method, bool)
      根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
      对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
      对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

    • type Method

    type Method struct {
        // Name是方法名。PkgPath是非导出字段的包路径,对导出字段该字段为""。
        // 结合PkgPath和Name可以从方法集中指定一个方法。
        Name    string
        PkgPath string
        Type  Type  // 方法类型
        Func  Value // 方法的值
        Index int   // 用于Type.Method的索引
    }
    

    Method代表一个方法。

    Value

    Value为go值提供了反射接口。

    不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。

    Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回<invalid Value>,所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

    如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。

    获取Value对象的方法:

    func ValueOf(i interface{}) Value
    

    ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。

    例如:

    str := "this is string"
    val := reflect.ValueOf(str)
    

    ** Value中常用的方法:**

    • Kind()
    func (v Value) Kind() Kind
    

    Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid

    • func (Value) IsValid
    func (v Value) IsValid() bool
    

    IsValid返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

    • func (Value) IsNil
    func (v Value) IsNil() bool
    

    IsNil报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。注意IsNil并不总是等价于go语言中值与nil的常规比较。例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。

    • func (Value) Type
    func (v Value) Type() Type
    

    返回v持有的值的类型的Type表示。

    • func (Value) Elem
    func (v Value) Elem() Value
    

    Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

    • func (Value) NumField
    func (v Value) NumField() int
    

    返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic

    • func (Value) Field
    func (v Value) Field(i int) Value
    

    返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic

    • func (Value) FieldByIndex
    func (v Value) FieldByIndex(index []int) Value
    

    返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic

    • func (Value) FieldByName
    func (v Value) FieldByName(name string) Value
    

    返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。

    • func (Value) NumMethod
    func (v Value) NumMethod() int
    

    返回v持有值的方法集的方法数目。

    • func (Value) Method
    func (v Value) Method(i int) Value
    

    返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。

    • func (Value) MethodByName
    func (v Value) MethodByName(name string) Value
    

    返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值。

    • func (Value) Call
    func (v Value) Call(in []Value) []Value
    

    Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。如果v持有值是可变参数函数,Call方法会自行创建一个代表可变参数的切片,将对应可变参数的值都拷贝到里面。

    • func (Value) CallSlice
    func (v Value) CallSlice(in []Value) []Value
    

    CallSlice调用v持有的可变参数函数,会将切片类型的in[len(in)-1](的成员)分配给v的最后的可变参数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值,可变参数函数的可变参数位置提供一个切片并跟三个点号代表"解切片")。如果v的Kind不是Func或者v的持有值不是可变参数函数,会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。

    • func (Value) Interface
    func (v Value) Interface() (i interface{})
    

    本方法返回v当前持有的值(表示为/保管在interface{}类型),等价于:

    var i interface{} = (v's underlying value)
    

    如果v是通过访问非导出结构体字段获取的,会导致panic。
    另外还有很多类似这个函数的功能可以获取value的值。

    func (v Value) String() string
    func (v Value) Bool() bool
    func (v Value) Int() int64
    func (v Value) Uint() uint64
    func (v Value) Float() float64
    func (v Value) Complex() complex128
    func (v Value) Bytes() []byte
    ...
    
    • func (Value) CanSet
    func (v Value) CanSet() bool
    

    如果v持有的值可以被修改,CanSet就会返回真。只有一个Value持有值可以被寻址同时又不是来自非导出字段时,它才可以被修改。如果CanSet返回假,调用Set或任何限定类型的设置函数(如SetBool、SetInt64)都会panic。

    • 设置函数
    func (v Value) SetBool(x bool)
    func (v Value) SetInt(x int64)
    func (v Value) SetUint(x uint64)
    func (v Value) SetFloat(x float64)
    func (v Value) SetComplex(x complex128)
    func (v Value) SetBytes(x []byte)
    func (v Value) SetString(x string)
    ...
    

    例子

    说了这么多,看下实例吧
    对象:

    package demo
    
    import (
        "fmt"
    )
    
    type Address struct{
        City string
        Area string
    }
    
    type Student struct{
        Address
        Name string
        Age int
    }
    
    func (this Student) Say(){
        fmt.Println("hello, i am ", this.Name, "and i am ", this.Age)
    }
    
    func (this Student) Hello(word string){
        fmt.Println("hello", word, ". i am ", this.Name)
    }
    

    有关反射函数demo

    package demo
    
    import (
        "fmt"
        "reflect"
    )
    
    /*
      获取对象的信息
    */
    func StructInfo(o interface{}){
        //获取对象的类型
        t := reflect.TypeOf(o)
        fmt.Println(t.Name(), "object type: ", t.Name())
    
        if k := t.Kind(); k != reflect.Struct{
            fmt.Println("the object is not a struct, but it is", t.Kind())
            return
        }
    
        //获取对象的值
        v := reflect.ValueOf(o)
        fmt.Println(t.Name(), "object value: ", v)
    
        //获取对象的字段
        fmt.Println(t.Name(), "fields: ")
        for i := 0; i < t.NumField(); i++{
            f := t.Field(i)
            val := v.Field(i).Interface()
            fmt.Printf("%6s:%v = %v \n", f.Name, f.Type, val)
            //通过递归调用获取子类型的信息
            t1 := reflect.TypeOf(val)
            if k := t1.Kind(); k == reflect.Struct{
                StructInfo(val)
            }
        }
        //获取对象的函数
        fmt.Println(t.Name(), "methods: ", t.NumMethod())
        for i := 0; i < t.NumMethod(); i++{
            m := t.Method(i)
            fmt.Printf("%10s:%v \n", m.Name, m.Type)
        }
    }
    
    /*
      匿名字段的反射
    */
    func Annoy(o interface{}){
        t := reflect.TypeOf(o)
        for i := 0; i < t.NumField(); i++{
            f := t.Field(i)
            fmt.Printf("%10s:%#v \n", f.Name, f)
        }
    }
    
    /*
      通过反射设置字段
    */
    func ReflectSet(o interface{}){
        v := reflect.ValueOf(o)
        if v.Kind() == reflect.Ptr && !v.Elem().CanSet(){
            fmt.Println("修改失败")
            return
        }
        v = v.Elem()
        //获取字段
        f := v.FieldByName("Name")
        if !f.IsValid(){
            fmt.Println("修改失败")
            return
        }
        //设置值
        if f.Kind() == reflect.String{
            f.SetString("chairis")
        }
    }
    
    /* 
      通过反射调用函数
    */
    func ReflectMethod(o interface{}){
        v := reflect.ValueOf(o)
        //无参函数调用
        m1:= v.MethodByName("Say")
        m1.Call([]reflect.Value{})
    
        //有参函数调用
        m2 := v.MethodByName("Hello")
        m2.Call([]reflect.Value{reflect.ValueOf("iris")})
    }
    

    上述代码一共有以下这几个函数:
    StructInfo:是用来说明type, 和value的使用方式的按照以下方式调用:

    package main
    
    import (
        . "stu_demo/demo"
    )
    
    func main(){
        stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
        StructInfo(stu)
    }
    

    结果如下:


    运行结果

    Annoy : 用来展示匿名字段的函数。
    照以下方式调用:

    package main
    
    import (
        . "stu_demo/demo"
    )
    
    func main(){
        stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
        Annoy(stu)
    }
    

    结果如下:


    运行结果

    ReflectSet : 是用来实现通过反射来修改字段值的函数。
    按照以下调用:

    package main
    
    import (
        "fmt"
        . "stu_demo/demo"
    )
    
    func main(){
        stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
        fmt.Println("before: ", stu)
        ReflectSet(&stu)
        fmt.Println("after: ", stu)
    }
    

    结果如下:


    运行结果

    ReflectMethod : 是用来实现通过反射调用函数的方法。
    按照以下调用:

    package main
    
    import (
        . "stu_demo/demo"
    )
    
    func main(){
        stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
        ReflectMethod(stu)
    }
    

    得以下结果:


    运行结果

    源码

    github 源码地址

    转载请注明出处:Golang 学习笔记(11)—— 反射

    目录
    上一节:Golang 学习笔记(10)—— mysql操作
    下一节:Golang 学习笔记(12)—— ORM实现

    相关文章

      网友评论

        本文标题:Golang 学习笔记(11)—— 反射

        本文链接:https://www.haomeiwen.com/subject/rjysnxtx.html