zoukankan      html  css  js  c++  java
  • golang reflect知识集锦

    反射之结构体tag

    链接

    1. 通过v.Field(i).Tag 获取结构体字段的field
    2. 通过v.Field(i).Tag.Get("id") 获取结构体字段中的特定信息
    3. func(tag StructTag)Lookup(key string)(value string,ok bool)
      根据 Tag 中的键,查询值是否存在。
    package main
    import (
        "fmt"
        "reflect"
    )
    func main() {
        // 声明一个空结构体
        type cat struct {
            Name string
            // 带有结构体tag的字段
            Type int `json:"type" id:"100"`
        }
        // 创建cat的实例
        ins := cat{Name: "mimi", Type: 1}
        // 获取结构体实例的反射类型对象
        typeOfCat := reflect.TypeOf(ins)
        // 遍历结构体所有成员
        for i := 0; i < typeOfCat.NumField(); i++ {
            // 获取每个成员的结构体字段类型
            fieldType := typeOfCat.Field(i)
            // 输出成员名和tag
            fmt.Printf("name: %v  tag: '%v'
    ", fieldType.Name, fieldType.Tag)
        }
        // 通过字段名, 找到字段类型信息
        if catType, ok := typeOfCat.FieldByName("Type"); ok {
            // 从tag中取出需要的tag
            fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
        }
    }
    

    Types vs Kinds

    Golang学习 - reflect 包
    Golang的反射reflect深入理解和示例
    系列文章

    • kind 指的是golang 内置的类型,如int, int32等,在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。内置类型如下
    内置类型如下
        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
    )
    
    通过reflect.ValueOf(x)或者reflect.TypeOf(x)获取对象后,对其执行v.Kind(), 结果与reflect.TypeOf(x)相同 reflect.Kind例子
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	for _, v := range []interface{}{"hi", 42, func() {}} {
    		switch v := reflect.ValueOf(v); v.Kind() {
    		case reflect.String:
    			fmt.Println(v.String())
    		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    			fmt.Println(v.Int())
    		default:
    			fmt.Printf("unhandled kind %s", v.Kind())
    		}
    	}
    
    }
    
    
    hi    
    42    
    unhandled kind func
    
    • type 指的是使用type定义的类型
    type mystruct struct {
      name string
      age int
    }
    func main() {
      ms := mystruct{"test", 100}
      fmt.Println(ms)
    }
    
    

    ms的type为自定义类型mystruct,而kind则为struct

    fmt.Println(reflect.TypeOf(ms))
    fmt.Println(reflect.ValueOf(ms))
    // 输出
    // main.mystruct
    // {test 100}
    

    总之: kind是go runtime和compiler为变量分配内存或为函数分配堆栈时是使用的概念,如Int8等。而Type则是Go编程时使用的概念。

    Type is the user defined metadata about data or function in a Go program. Kind is the compiler and runtime defined metadata about data or function in a Go program.
    Kind is used by the runtime and compiler to allocate the memory layout for a variable or to allocate the stack layout for a function.

    reflect.Type vs reflect.Value

    golang为类型的存储方式为(type, value), type有static type,对应上面的Type,有concrete type,对应上面的Kind。注意只有interface类型才有反射一说。value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

    • reflect.Type
      直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
    • reflect.Value
      直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值

    通过v=reflect.ValueOf(inst)获取到reflect.Value, 然后通过v.Kind == reflect.Int等判断是否是go基础类型

    func formatAtom(v reflect.Value) string {
        switch v.Kind() {
        case reflect.Invalid:
            return "invalid"
        case reflect.Int, reflect.Int8, reflect.Int16,
            reflect.Int32, reflect.Int64:
            return strconv.FormatInt(v.Int(), 10)
        case reflect.Uint, reflect.Uint8, reflect.Uint16,
            reflect.Uint32, reflect.Uint64, reflect.Uintptr:
            return strconv.FormatUint(v.Uint(), 10)
        // ...floating-point and complex cases omitted for brevity...
        case reflect.Bool:
            return strconv.FormatBool(v.Bool())
        case reflect.String:
            return strconv.Quote(v.String())
        case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
            return v.Type().String() + " 0x" +
                strconv.FormatUint(uint64(v.Pointer()), 16)
        default: // reflect.Array, reflect.Struct, reflect.Interface
            return v.Type().String() + " value"
        }
    }
    

    一个运用反射调用方法的例子

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type User struct {
    	Id   int
    	Name string
    	Age  int
    }
    
    func (u User) ReflectCallFuncHasArgs(name string, age int) {
    	fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
    }
    
    func (u User) ReflectCallFuncNoArgs() {
    	fmt.Println("ReflectCallFuncNoArgs")
    }
    
    // 如何通过反射来进行方法的调用?
    // 本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call
    
    func main() {
    	user := User{1, "Allen.Wu", 25}
    	
    	// 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
    	getValue := reflect.ValueOf(user)
    
    	// 一定要指定参数为正确的方法名
    	// 2. 先看看带有参数的调用方法
    	methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
    	args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
    	methodValue.Call(args)
    
    	// 一定要指定参数为正确的方法名
    	// 3. 再看看无参数的调用方法
    	methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
    	args = make([]reflect.Value, 0)
    	methodValue.Call(args)
    }
    
    
    运行结果:
    ReflectCallFuncHasArgs name:  wudebao , age: 30 and origal User.Name: Allen.Wu
    ReflectCallFuncNoArgs
    

    2019/4/20 补充

    《Go语言实战》笔记(二十四) | Go 反射

    reflect.Value转原始类型

    上面的例子我们可以通过reflect.ValueOf函数把任意类型的对象转为一个reflect.Value,那我们如果我们想逆向转过回来呢,其实也是可以的,reflect.Value为我们提供了Inteface方法来帮我们做这个事情。继续接上面的例子:

    u1:=v.Interface().(User)
    fmt.Println(u1)
    

    这样我们就又还原为原来的User对象了,通过打印的输出就可以验证。这里可以还原的原因是因为在Go的反射中,把任意一个对象分为reflect.Value和reflect.Type,而reflect.Value又同时持有一个对象的reflect.Value和reflect.Type,所以我们可以通过reflect.Value的Interface方法实现还原。现在我们看看如何从一个reflect.Value获取对应的reflect.Type。

    t1:=v.Type()
    fmt.Println(t1)
    

    如上例中,通过reflect.Value的Type方法就可以获得对应的reflect.Type。

    获取类型底层类型

    底层的类型是什么意思呢?其实对应的主要是基础类型,接口、结构体、指针这些,因为我们可以通过type关键字声明很多新的类型,比如上面的例子,对象u的实际类型是User,但是对应的底层类型是struct这个结构体类型,我们来验证下。

    fmt.Println(t.Kind())

    遍历字段和方法

    通过反射,我们可以获取一个结构体类型的字段,也可以获取一个类型的导出方法,这样我们就可以在运行时了解一个类型的结构,这是一个非常强大的功能。

    for i:=0;i<t.NumField();i++ {
        fmt.Println(t.Field(i).Name)
    }    
    for i:=0;i<t.NumMethod() ;i++  {
        fmt.Println(t.Method(i).Name)
    }
    

    这个例子打印出结构体的所有字段名以及该结构体的方法。NumField方法获取结构体有多少个字段,然后通过Field方法传递索引的方式,循环获取每一个字段,然后打印出他们的名字。

    同样的对于方法也类似,这里不再赘述。

    获取值

    当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值

    // 声明整型变量a并赋初值
        var a int = 1024
        // 获取变量a的反射值对象
        valueOfA := reflect.ValueOf(a)
        // 获取interface{}类型的值, 通过类型断言转换
        var getA int = valueOfA.Interface().(int)
        // 获取64位的值, 强制类型转换为int类型
        var getA2 int = int(valueOfA.Int())
    

    修改字段的值

    假如我们想在运行中动态的修改某个字段的值有什么办法呢?一种就是我们常规的有提供的方法或者导出的字段可以供我们修改,还有一种是使用反射,这里主要介绍反射。

    func main() {
        x:=2
        v:=reflect.ValueOf(&x)
        v.Elem().SetInt(100)
        fmt.Println(x)
    }
    

    以上就是通过反射修改一个变量的例子。

    因为reflect.ValueOf函数返回的是一份值的拷贝,所以前提是我们是传入要修改变量的地址。
    其次需要我们调用Elem方法找到这个指针指向的值。
    最后我们就可以使用SetInt方法修改值了。

    以上有几个重点,才可以保证值可以被修改,Value为我们提供了CanSet方法可以帮助我们判断是否可以修改该对象。

    动态调用方法

    结构体的方法我们不光可以正常的调用,还可以使用反射进行调用。要想反射调用,我们先要获取到需要调用的方法,然后进行传参调用,如下示例:

    func main() {
        u:=User{"张三",20}
        v:=reflect.ValueOf(u)
    
        mPrint:=v.MethodByName("Print")
        args:=[]reflect.Value{reflect.ValueOf("前缀")}
        fmt.Println(mPrint.Call(args))
    
    }
    type User struct{
        Name string
        Age int
    }
    func (u User) Print(prfix string){
        fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
    }
    

    MethodByName方法可以让我们根据一个方法名获取一个方法对象,然后我们构建好该方法需要的参数,最后调用Call就达到了动态调用方法的目的。

    获取到的方法我们可以使用IsValid 来判断是否可用(存在)。

    这里的参数是一个Value类型的数组,所以需要的参数,我们必须要通过ValueOf函数进行转换。

  • 相关阅读:
    50个提高PHP程序运行效率的方法
    虚拟主机FTP上传文件为什么要用二进制上传
    Status Bar 总结
    TableView 总结
    阿里Java开发手册(泰山版)个人记录
    下载excel模板
    微信公众号-发送模板消息
    ffmpeg获取视频时长
    微信公众号授权
    根据word模板生成word、转换成pdf、打成war包
  • 原文地址:https://www.cnblogs.com/linyihai/p/11602484.html
Copyright © 2011-2022 走看看