zoukankan      html  css  js  c++  java
  • golang的reflect

    引用自 http://www.jb51.net/article/115002.htm

    和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的。反射操作所需要的全部信息都源自接口变量。接口变量除存储自身类型外,还会保存实际对象的类型数据。

    reflect包有两个接口:reflect.Type和reflect.Value。这两个反射入口函数,会将任何传入的对象转换为对应的接口类型。

    func TypeOf(i interface{}) Type
    func ValueOf(i interface{}) Value
    

    一、Type(类型)

    源码文件里type的注释:
    // Type is the representation of a Go type.
    //
    // Not all methods apply to all kinds of types. Restrictions,
    // if any, are noted in the documentation for each method.
    // Use the Kind method to find out the kind of type before
    // calling kind-specific methods. Calling a method
    // inappropriate to the kind of type causes a run-time panic.
    //
    // Type values are comparable, such as with the == operator,
    // so they can be used as map keys.
    // Two Type values are equal if they represent identical types.

     在面对类型时,需要区分 Type 和 Kind。前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别 -- 基类型。

    type X int
    func main() {
        var a X = 100
        t := reflect.TypeOf(a)
        fmt.Println(t)
        fmt.Println(t.Name(), t.Kind())
    }
    
    
    输出:X int
    

      

    除通过实际对象获取类型外,也可直接构造一些基础复合类型。

    func main() {
        a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
        m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
        fmt.Println(a, m)
    }
    
    输出:[10]uint8 map[string]int
    // 还有ArrayOf, ChanOf, MapOf and SliceOf

      

    传入对象 应区分 基类型 和 指针类型,因为它们并不属于同一类型。方法 Elem() 返回 指针、数组、切片、字典(值)或 通道的 基类型。

    func main() {
       x := 100
        tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
        fmt.Println(tx, tp, tx == tp)
    
        fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
        fmt.Println(reflect.TypeOf([]int32{}).Elem())
    }
    
    输出:
    int *int false
    int  
    int32
    

      

      

    只有在获取 结构体指针 的 基类型 后,才能遍历它的字段。对于匿名字段,可用多级索引(按照定义顺序)直接访问。反射能探知当前包或外包的非导出结构成员。

    type user struct {
        name string
        age  int
    }
    type manager struct {
        user
        title string
    }
    func main() {
        var m manager
        t := reflect.TypeOf(&m)
        if t.Kind() == reflect.Ptr {
            t = t.Elem()
        }
      // age := t.FieldByIndex([]int{0, 1}) // 按多级索引查找匿名字段
      // fmt.Println(age.Name, age.Type) 输出:age int
        for i := 0; i < t.NumField(); i++ {
            f := t.Field(i)
            fmt.Println(f.Name, f.Type, f.Offset)
            if f.Anonymous { // 输出匿名字段结构
                for x := 0; x < f.Type.NumField(); x++ {
                    af := f.Type.Field(x)
                    fmt.Println(" ", af.Name, af.Type)
                }
            }
      }
    }
    
    输出:
    user main.user 0
     name string
     age int
    title string 24
    

      

      

    同样地,输出方法集时,一样区分 基类型 和 指针类型。

    type A int
    type B struct {
        A
    }
    func (A) av() {}
    func (*A) ap() {}
    func (B) bv() {}
    func (*B) bp() {}
    func main() {
        var b B
        t := reflect.TypeOf(&b)
        s := []reflect.Type{t, t.Elem()}
        for _, t2 := range s {
            fmt.Println(t2, ":")
            for i := 0; i < t2.NumMethod(); i++ {
                fmt.Println(" ", t2.Method(i))
            }
        }
    }
    
    输出:
    *main.B :
      {ap main func(*main.B) <func(*main.B) Value> 0}
      {av main func(*main.B) <func(*main.B) Value> 1}
      {bp main func(*main.B) <func(*main.B) Value> 2}
      {bv main func(*main.B) <func(*main.B) Value> 3}   
    main.B :
      {av main func(*main.B) <func(*main.B) Value> 0} 
      {bv main func(*main.B) <func(*main.B) Value> 1}
    

      

      

    可用反射提取 struct tag,还能自动分解。其常用于 ORM 映射,或数据格式验证。

    type user struct {
        name string `field:"name" type:"varchar(50)"`
        age  int `field:"age" type:"int"`
    }
    func main() {
        var u user
        t := reflect.TypeOf(u)
        for i := 0; i < t.NumField(); i++ {
            f := t.Field(i)
            fmt.Printf("%s: %s %s
    ", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
        }
    }
    
    输出:
    name: name varchar(50)
    age: age int 
    

      

     

    辅助判断方法 Implements()、ConvertibleTo、AssignableTo() 都是运行期进行 动态调用 和 赋值 所必需的。

    二、值(Value)

     和 Type 获取类型信息不同,Value 专注于对象实例数据读写。

    go源码里面Value的注释:
    // Value is the reflection interface to a Go value.
    //
    // Not all methods apply to all kinds of values. Restrictions,
    // if any, are noted in the documentation for each method.
    // Use the Kind method to find out the kind of value before
    // calling kind-specific methods. Calling a method
    // inappropriate to the kind of type causes a run time panic.
    //
    // The zero Value represents no value.
    // Its IsValid method returns false, its Kind method returns Invalid,
    // its String method returns "<invalid Value>", and all other methods panic.
    // Most functions and methods never return an invalid value.
    // If one does, its documentation states the conditions explicitly.
    //
    // A Value can be used concurrently by multiple goroutines provided that
    // the underlying Go value can be used concurrently for the equivalent
    // direct operations.
    //
    // To compare two Values, compare the results of the Interface method.
    // Using == on two Values does not compare the underlying values
    // they represent.

    接口变量会复制对象,且是 unaddressable 的,所以要想修改目标对象,就必须使用指针。

    func main()  {
        a := 100
      fmt.Println(a)
      va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem() 
      fmt.Println(va.CanAddr(), va.CanSet()) 
      fmt.Println(vp.CanAddr(), vp.CanSet())
      vp.Set(reflect.ValueOf(10))
      fmt.Println(a)
    } 
    
    输出: 
    100
    false false 
    true true
    10

      

    就算传入指针,一样需要通过 Elem() 获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的。

    不能对非导出字段直接进行设置操作,无论是当前包还是外包。

    复合类型对象设置示例:(chan类型)

    func main()  {
        c := make(chan int, 4)
        v := reflect.ValueOf(c)
        if v.TrySend(reflect.ValueOf(100)) {
            fmt.Println(v.TryRecv())
        }
    }
    

      

    接口有两种 nil 状态,这一直是个潜在麻烦。解决方法是用 IsNil() 判断值是否为 nil。

    func main()  {
        var a interface{} = nil
        var b interface{} = (*int)(nil)
        fmt.Println(a == nil) // 如果此条件为true,就不能执行reflect.ValueOf(a).IsNil()
        fmt.Println(b == nil, reflect.ValueOf(b).IsNil())
    }
    
    输出:
    true
    false true
    

      

      

    三、方法

    动态调用方法,谈不上有多麻烦。只须按 In 列表准备好所需参数即可。

    type X struct {}
    func (X) Test(x, y int) (int, error)  {
        return x + y, fmt.Errorf("err: %d", x + y)
    }
    func main()  {
        var a X
        v := reflect.ValueOf(&a)
        m := v.MethodByName("Test")
        in := []reflect.Value{
            reflect.ValueOf(1),
            reflect.ValueOf(2),
        }
        out := m.Call(in)    // 对于变参来说,用 CallSlice() 要更方便一些。
        for _, v := range out {
            fmt.Println(v)
        }
    }
    
    输出:
    3
    err: 3
    

      

      

    四、构建

    不同结构体字段赋值函数如下。

    函数说明:传入参数from和to都是指针变量。只支持不同结构体同名且相同值类型字段赋值(可优化,加一个字段映射,并兼容int与string相互赋值)。

    func CopyStruct (from, to interface{}) (bool) {
    
    	typFrom := reflect.TypeOf(from)
    	valFrom := reflect.Indirect(reflect.ValueOf(from))
    
    	typTo := reflect.TypeOf(to)
    	valTo := reflect.Indirect(reflect.ValueOf(to))
    
    	if typFrom.Kind() != reflect.Ptr || typTo.Kind() != reflect.Ptr ||
    	valFrom.Kind() != reflect.Struct || valTo.Kind() != reflect.Struct {
    		return false
    	}
    
    	typTo = typTo.Elem()
    	typFrom = typFrom.Elem()
    	for i := 0; i < typTo.NumField(); i ++ {
    		toField := typTo.Field(i)
    		toFieldName := toField.Name
    		_, exists := typFrom.FieldByName(toFieldName)
    		if !exists || !valTo.FieldByName(toFieldName).CanSet() {
    			continue
    		}
    
    		if valFrom.FieldByName(toFieldName).Kind() == valTo.FieldByName(toFieldName).Kind() {
    			valTo.FieldByName(toFieldName).Set(valFrom.FieldByName(toFieldName))
    		}
    	}
    	return true
    }
    

      

  • 相关阅读:
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    asp.net session对象的持久化
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    小白也能看懂的约瑟夫环问题
  • 原文地址:https://www.cnblogs.com/gauze/p/8984581.html
Copyright © 2011-2022 走看看