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'\n", 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函数进行转换。

猜你喜欢

转载自www.cnblogs.com/linyihai/p/11602484.html