目录
- 反射之结构体tag
- Types vs Kinds
- reflect.Type vs reflect.Value
- 2019/4/20 补充
- reflect.Value转原始类型
- 获取类型底层类型
- 遍历字段和方法
- 获取值
- 修改字段的值
- 动态调用方法
反射之结构体tag
- 通过v.Field(i).Tag 获取结构体字段的field
- 通过v.Field(i).Tag.Get("id") 获取结构体字段中的特定信息
- 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
- 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 )
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 补充
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函数进行转换。
原文地址:https://www.cnblogs.com/linyihai/p/11602484.html