美文网首页
Goalng下的反射模块reflect学习使用

Goalng下的反射模块reflect学习使用

作者: 9c46ece5b7bd | 来源:发表于2018-04-27 21:31 被阅读410次

    反射reflection

    反射

    • 可以大大提高程序的灵活性,使得interface{}有更大的发挥余地
    • 反射使用TypeOf和ValueOf函数从接口中获取目标对象信息
    • 反射会将匿名字段作为独立字段(匿名字段的本质)
    • 想要利用反射修改对象状态,前提是interface.data是settable,即pointer-interface
    • 通过反射可以"动态" 调用方法

    常用的类型、函数和方法

    //返回动态类型i的类型,如果i是一个空结构体类型,TypeOf将返回nil
    func TypeOf(i interface{}) Type
    
    //Type 接口类型
    type Type interface {
        Align() int
        FieldAlign() int
        //指定结构体中方法的下标,返回某个方法的对象,需要注意的是返回的Method是一个独立的结构体
        Method(int) Method
        /*
        type Method struct {
            Name string
            PkgPath string
            Type Type
            Func Value
            Index int
        }
        */
        
        
        MethodByName(string) (Method, bool)
        
        //返回该结构体类型的方法下标
        NumMethod() int
        //返回类型的名称,即动态类型i的名称
        Name() string
        PkgPath() string
        Size() uintptr
        String() string
        Kind() Kind
        Implements(u Type) bool
        AssignableTo(u Type) bool
        ConvertibleTo(u Type) bool
        Comparable() bool
        Bits() int
        ChanDir() ChanDir
        IsVariadic() bool
        Elem() Type
        //返回结构体类型第i个字段
        Field(i int) StructField
        //StructField结构体
        //type StructField struct {
        // Name string
        // PkgPath string
        // Type Type
        // Tag  StructTag
        // Offset uintptr
        // Index []int
        // Anonymous bool
        
        //根据结构体字段索引获取嵌入字段的结构体信息
        FieldByIndex(index []int) StructField
        
        FieldByName(name string) (StructField, bool)
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        In(i int) Type
        Key() Type
        Len() int
        //返回动态类型i(结构体字段)的字段总数
        NumField() int
        NumIn() int
        NumOut() int
        Out(i int) Type
    }
    
    //返回接口i的一个初始化的新值.ValueOf(nil)返回一个零值
    func ValueOf(i interface{}) Value
    
    // Value结构体
    type Value struct {
        
    }
    // Value结构体的一些方法
    // 返回结构体v中的第i个字段。如果v的类型不是结构体或者i超出了结构体的范围,则会出现panic
    func (v Value) Field(i int) Value
    
    //以接口类型返回v的当前值
    func (v Value) Interface() (i interface{})
    //等价于.
    var i interface{} = (v's underlying value)
    
    
    //通过反射方式修改结构体对象的一些方法
    
    //返回接口v包含或者指针v包含的值
    func (v Value) Elem() Value
    //判断该接口v是否可以被set修改
    func (v Value) CanSet() bool
    
    //使用另外一个反射接口去修改反射值
    func (v Value) Set(x Value)
    //其他不同类型的Set
    func (v Value) SetBool(x bool)
    func (v Value) SetBytes(x []byte)
    func (v Value) SetFloat(x float64)
    func (v Value) SetInt(x int64)
    //设置结构体对象v的长度为n
    func (v Value) SetLen(n int)
    func (v Value) SetString(x string)
    
    
    //一些辅助方法
    //返回反射结构体的Value的类型.如果v为零值,IsValid将返回false
    func (v Value) Kind() Kind
    //判断value是否为有效值,通常用在判断某个字段是否在反射体的Value中
    func (v Value) IsValid() bool
    
    //Kind常量
    type Kind uint
    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
    )
    
    
    
    
    
    
    

    反射的基本操作

    通过反射来获取结构体字段的名称以及其他相关信息。

    $ cat test-reflect.go
    package main
    import (
        "fmt"
        "reflect"
    )
    
    //定义结构体
    type User struct{
        Id    int
        Name  string
        Age   int
        }
    //定义结构体方法
    func (u User) Hello(){
        fmt.Println("Hello xuxuebiao")
        }
    
    func main() {
       u := User{1,"bgops",25}
       Info(u)
       u.Hello()
    }
    
    //定义一个反射函数,参数为任意类型
    func Info(o interface{}) {
        //使用反射类型获取o的Type,一个包含多个方法的interface
        t := reflect.TypeOf(o)
        //打印类型o的名称
        fmt.Println("type:",t.Name())
    
        //使用反射类型获取o的Value,一个空的结构体
        v := reflect.ValueOf(o)
        fmt.Println("Fields:")
    
        //t.NumField()打印结构体o的字段个数(Id,Name,Age共三个)
        for i :=0;i< t.NumField();i++{
            //根据结构体的下标i来获取结构体某个字段,并返回一个新的结构体
            /**
            type StructField struct {
                Name string
                PkgPath string
                Type      Type
                Tag       StructTag
                Offset    uintptr
                Index     []int
                Anonymous bool
            }
            **/
            f := t.Field(i)
    
            //使用结构体方法v.Field(i)根据下标i获取字段Value(Id,Name,Age)
            //在根据Value的Interface()方法获取当前的value的值(interface类型)
            val := v.Field(i).Interface()
            fmt.Printf("%6s:%v = %v\n",f.Name,f.Type,val)
            }
    
        //使用t.NumMethod()获取所有结构体类型的方法个数(只有Hello()一个方法)
        //接口Type的方法NumMethod() int
        for i := 0;i < t.NumMethod();i++ {
            //使用t.Method(i)指定方法下标获取方法对象。返回一个Method结构体
            //Method(int) Method
            m := t.Method(i)
            //打印Method结构体的相关属性
            /*
            type Method struct {
                  Name    string
                  PkgPath string
                  Type    Type
                  Func    Value
                  Index   int
            }
            */
            fmt.Printf("%6s:%v\n",m.Name,m.Type)
            }
        }
        
    $ go run test-reflect.go
    type: User
    Fields:
        Id:int = 1
      Name:string = bgops
       Age:int = 25
     Hello:func(main.User)
    Hello xuxuebiao
    

    注意:我们上面的示例是使用值类型进行进行反射构造的。如果是指针类型的话,我们需要使用reflect.Struct字段进行判断接口类型的Kind()方法

    if k := t.Kind();k != reflect.Struct {
        fmt.Println("非值类型的反射")
        return
    }
    

    匿名字段的反射以及嵌入字段

    注意:反射会将匿名字段当做独立的字段去处理,需要通过类型索引方式使用FieldByIndex方法去逐个判断

    //根据指定索引返回对应的嵌套字段
    FieldByIndex(index []int) StructField
    
    type StructField struct {
        Name    string
        PkgPath string
        Type    Type
        Tag     StructTag
        Offset  uintptr
        Index   []int
        Anonymous   bool //是否为匿名字段
    }
    

    示例:

    $ cat anonymous-reflect.go
    package main
    import (
        "fmt"
        "reflect"
    )
    
    type User struct {
        Id    int
        Name  string
        Age   int
    }
    
    type Manager struct {
        User
        title string
    }
    
    func main() {
        //注意匿名字段的初始化操作
        m :=  Manager{User: User{1,"biaoge",24},title:"hello biao"}
        t := reflect.TypeOf(m)
    
        fmt.Printf("%#v\n",t.FieldByIndex([]int{0}))
        fmt.Printf("%#v\n",t.FieldByIndex([]int{1}))
        fmt.Printf("%#v\n",t.FieldByIndex([]int{0,0}))
        fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1}))
    
    }
    
    $ go run anonymous-reflect.go
    reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x97260), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
    reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x8bda0), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}
    reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x8b8a0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
    reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x8bda0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
    

    通过反射修改目标对象

    通过反射的方式去修改对象的某个值。需要注意的亮点是,首先,需要找到对象相关的名称,其次需要找到合适的方法去修改相应的值。

    $ cat mod-value-reflect.go
    package main
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        x := 123
        v := reflect.ValueOf(&x)
        v.Elem().SetInt(5256)
        fmt.Println(x)
    }
    
    $ go run mod-value-reflect.go
    5256
    

    修改I的时候需要找到对象字段的名称;并且判断类型,使用相对正确的类型修改值

    v.FieldByName("Name");f.Kind() == reflect.String {
        f.SetString("test string")
    }
    

    判断是否找到正确的字段名称:

    f := v.FieldByName("Name1")
    //判断反射对象Value中是否找到Name1字段
    if !f.IsValid() {
        fmt.Println("bad field")
        return
    }
    

    示例:

    $ cat mod-value-reflect.go
    package main
    import (
        "fmt"
        "reflect"
    )
    
    type User struct {
        Id  int
        Name string
        Age int
    }
    
    //使用反射方式对结构体对象的修改有两个条件
    //1.通过指针
    //2.必须是可set的方法
    func main() {
        num := 123
        numv := reflect.ValueOf(&num)
        //通过Value的Elem()和SetX()方法可直接对相关的对象进行修改
        numv.Elem().SetInt(666)
        fmt.Println(num)
    
        u := User{1,"biao",24}
        uu := reflect.ValueOf(&u)
        //Set()后面的必须是值类型
        //func (v Value) Set(x Value)
        test := User{2,"bgops",2}
        testv := reflect.ValueOf(test)
        uu.Elem().Set(testv)
        fmt.Println("Change the test to u with Set(x Value)",uu)
    
        //此时的U已经被上面那个uu通过指针的方式修改了
        Set(&u)
        fmt.Println(u)
    }
    
    func Set(o interface{}) {
        v := reflect.ValueOf(o)
        //判断反射体值v是否是Ptr类型并且不能进行Set操作
        if v.Kind() == reflect.Ptr && ! v.Elem().CanSet() {
            fmt.Println("xxx")
            return
            //初始化对象修改后的返回值(可接受v或v的指针)
            } else {
                v = v.Elem()
            }
        //按照结构体对象的名称进行查找filed,并判断类型是否为string,然后进行Set
        if f := v.FieldByName("Name"); f.Kind() == reflect.String {
              f.SetString("BYBY")
            }
        }
        
    $ go run mod-value-reflect.go
    666
    Change the test to u with Set(x Value) &{2 bgops 2}
    {2 BYBY 2}
    

    通过反射进行动态方法的调用

    使用反射的相关知识进行方法的动态调用

    $ cat method-reflect.go
    package main
    import (
        "fmt"
        "reflect"
    )
    
    type User struct {
        Id  int
        Name string
        Age int
    }
    
    func (u User) Hello(name string,id int) {
        fmt.Printf("Hello %s,my name is %s and my id is %d\n",name,u.Name,id)
    }
    
    func main() {
        u := User{1,"biaoge",24}
        fmt.Println("方法调用:")
        u.Hello("xuxuebiao",121)
    
        //获取结构体类型u的Value
        v := reflect.ValueOf(u)
        //根据方法名称获取Value中的方法对象
        mv := v.MethodByName("Hello")
    
        //构造一个[]Value类型的变量,使用Value的Call(in []Value)方法进行动态调用method
        //这里其实相当于有一个Value类型的Slice,仅一个字段
        args := []reflect.Value{reflect.ValueOf("xuxuebiao"),reflect.ValueOf(5256)}
        fmt.Println("通过反射动态调用方法:")
        //使用Value的Call(in []Value)方法进行方法的动态调用
        //func (v Value) Call(in []Value) []Value
        //需要注意的是当v的类型不是Func的化,将会panic;同时每个输入的参数args都必须对应到Hello()方法中的每一个形参上
        mv.Call(args)
    
    }
    
    $ go run method-reflect.go
    方法调用:
    Hello xuxuebiao,my name is biaoge and my id is 121
    通过反射动态调用方法:
    Hello xuxuebiao,my name is biaoge and my id is 5256
    

    相关文章

      网友评论

          本文标题:Goalng下的反射模块reflect学习使用

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