go reflect

反射

Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用类型的方法,以及直接对它们的布局进行操作,这种机制称为反射(reflection)。

一、relect.Type

1、什么是 relect.Type?

Go语言中任何变量都包含reflect.Typereflect.Value 两部分信息。

reflect.Type是声明变量时的类型,它在Go中被定义成接口。

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    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	//取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回nil的Value
    Field(i int) StructField
    FieldByIndex(index []int) StructField
    FieldByName(name string) (StructField, bool)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    In(i int) Type
    Key() Type
    Len() int
    NumField() int
    NumIn() int
    NumOut() int
    Out(i int) Type
    common() *rtype
    uncommon() *uncommonType
}

2、怎样区分reflect.Type (类型)与 reflect.Kind(种类)?

reflect.Type.Name() 就是声明变量时指定的类型。Go语言是一门静态类型的语言,每个变量都有一个静态类型,类型在编译的时候确定下来。不管变量的值如何改变,变量的类型永远不变。两个不同的静态类型之间,是不能赋值的(如果没有进行类型转换 )。

type MyInt int
var i int = 5
var j MyInt = 10
j = i // build error: cannot use i (type int) as type MyInt in assignment

reflect.KindGo中定义的内置类型(底层数据类型), 比如这些:

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

示例:区分TypeKind
package main

import (
	"fmt"
	"reflect"
)

type MyType int

type Stu struct {
	name string
	score float32
}

func test1(a interface{}) {
	fmt.Printf("the type of a is: %v\n", reflect.TypeOf(a))
	fmt.Printf("the kind of a is: %v\n\n", reflect.ValueOf(a).Kind())
}

func main() {
	var a Stu
	var b MyType
	test1(a)
	test1(b)
}

输出:

the type of a is: main.Stu
the kind of a is: struct

the type of a is: main.MyType
the kind of a is: int

3、指针反射

Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作。

reflect.Elem()返回的是Type

// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type

Go语言的反射中对所有指针变量的种类都是Ptr,但需要注意的是,指针变量的Type.Name()nil


示例:

func main() {
	type cat struct {
	}

	p := &cat{}
	typeOfCat := reflect.TypeOf(p)
    fmt.Printf("type: %v\n", typeOfCat)	// reflect.Type是interface类型,所以可传递给fmtr打印
	fmt.Printf("TypeName: %v, Kind: %v\n", typeOfCat.Name(), typeOfCat.Kind())
	fmt.Printf("TypeName: %v, Kind: %v\n", typeOfCat.Elem().Name(), typeOfCat.Elem().Kind())
}

输出:

type: *main.cat
TypeName: , Kind: ptr
TypeName: cat, Kind: struct

4、结构体反射

如果反射对象是结构体,则有以下方法获得结构体中的成员信息:

方法说明
Field(i int) StructField根据索引返回索引对应的结构体字段的信息,当值不是结构体或索引超界时发生宕机
NumField() int返回结构体成员字段数量,当类型不是结构体或索引超界时发生宕机
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值。当类型不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) (StructField,bool)根据匹配函数匹配需要的字段,当值不是结构体或索引超界时发生宕机

1)StructField (结构体字段)

reflect.TypeField() 方法返回StructField结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签StructTag等,而且还可以通过 StructFieldType 字段进一步获取结构体成员的类型信息

StructField 的结构如下:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路径
    Type      Type       // 字段反射类型对象
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否为匿名字段
}

示例:

func main() {
	type cat struct {
		Name string
		Color string
		Age int
		Home struct {
			Adderess string
			PostCode int
		}
	}

	ins := cat{
		Name: "Tom",
		Color: "Dark",
		Age: 5,
		Home: struct{
			Adderess string
			PostCode int
		}{
			"England",
			1024,
		},
	}

	sType := reflect.TypeOf(ins)

	for i:=0; i < sType.NumField(); i++ {
		filed := sType.Field(i)
		fmt.Printf("filedName: %s, Type: %v\n", filed.Name, filed.Type)
	}

	if addrType, ok := sType.FieldByName("Adderess"); ok {
		fmt.Printf("Name: %s, Type: %v", addrType.Name, addrType.Type)
	}
}

输出:

filedName: Name, Type: string
filedName: Color, Type: string
filedName: Age, Type: int
// Adderess属于Home这个成员
filedName: Home, Type: struct { Adderess string; PostCode int }

2)StructTag (结构体标签)

结构体成员信息reflect.StructField 结构中的Tag 被称为结构体标签StructTag。结构体标签是对结构体字段的额外信息标签。

type StructTag string

结构体标签的格式

`key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成;键与值使用冒号分隔,冒号后不能有空格,值用双引号括起来;键值对之间使用一个空格分隔。


从结构体标签中获取值

StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:

func (tag StructTag) Get(key string) string //根据 Tag 中的键获取对应的值。
func (tag StructTag) Lookup(key string) (value string, ok bool) //根据 Tag 中的键,查询值是否存在。

示例:

func test4() {
	type cat struct {
		Name string `json:"name" bson:"NAME"`
		Color string
	}

	ins := cat{
		Name: "Tom",
		Color: "Dark",
	}

	sType := reflect.TypeOf(ins)

	if filedName, ok := sType.FieldByName("Name"); ok {
		nameJson := filedName.Tag.Get("json")
		fmt.Printf("the json tag of Name is: %s\n", nameJson)
	}
}

输出:

the json tag of Name is: name



二、relect.Value


reflect.Value

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

// 方法:
pointer() unsafe.Pointer
Addr() Value	//		对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
Bool() bool		//		将值以 bool 类型返回
Bytes() []byte	//		将值以字节数组 []bytes 类型返回
runes() []rune
CanAddr() bool	//		表示值是否可寻址
CanSet() bool	//		返回值能否被修改。要求值可寻址且是导出的字段
Call(in []Value) []Value
CallSlice(in []Value) []Value
call(op string, in []Value) []Value
Cap() int
Close()
Complex() complex128
Elem() Value	//	取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value
Field(i int) Value
FieldByIndex(index []int) Value
FieldByName(name string) Value
FieldByNameFunc(match func(string) bool) Value
Float() float64		//	将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Index(i int) Value
Int() int64		//		将值以 int64 类型返回,所有有符号整型均可以此方式返回,可能需要类型转换
CanInterface() bool
Interface() (i interface{})	//	将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
InterfaceData() [2]uintptr
IsNil() bool	//	返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool	//	判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。
IsZero() bool
Kind() Kind
Len() int
MapIndex(key Value) Value
MapKeys() []Value
MapRange() *MapIter
Method(i int) Value
NumMethod() int
MethodByName(name string) Value
NumField() int
OverflowComplex(x complex128) bool
OverflowFloat(x float64) bool
OverflowInt(x int64) bool
OverflowUint(x uint64) bool
Pointer() uintptr
Recv() (x Value, ok bool)
recv(nb bool) (val Value, ok bool)
Send(x Value)
send(x Value, nb bool) (selected bool)
Set(x Value)
SetBool(x bool)
SetBytes(x []byte)
setRunes(x []rune)
SetComplex(x complex128)
SetFloat(x float64)
SetInt(x int64)
SetLen(n int)
SetCap(n int)
SetMapIndex(key Value, elem Value)
SetUint(x uint64)
SetPointer(x unsafe.Pointer)
SetString(x string)
Slice(i int, j int) Value
Slice3(i int, j int, k int) Value
String() string		//		将值以字符串类型返回
TryRecv() (x Value, ok bool)
TrySend(x Value) bool
Type() Type
Uint() uint64	//	将值以 uint 类型返回,所有无符号整型均可以此方式返回
UnsafeAddr() uintptr
assignTo(context string, dst *rtype, target unsafe.Pointer) Value
Convert(t Type) Value

1、获取特定类型的值

// sclice
Index(i int) Value	//	返回第i个元素,如果i越界会崩溃

//	struct
NumField() int		//	返回字段的数量
Field(i int) Value	//	返回第i个字段,如果i越界会崩溃

//	map
MapIndex(key Value) Value	//	返回key对应的值

//	ptr
Elem() Value

//	interface
//	先通过 IsNil 判断是否非空, 如果非空, 再通过 Elem() 获取动态值

2、判断Value中的值是否有效:

// *int的空指针
var a *int
fmt.Println("nil *int IsNil:", reflect.ValueOf(a).IsNil())
fmt.Println("nil *int IsValid:", reflect.ValueOf(nil).IsValid())

// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员 IsValid:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法 IsValid:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键 IsValid:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())

输出:

nil *int IsNil: true
nil *int IsValid: false
不存在的结构体成员 IsValid: false
不存在的结构体方法 IsValid: false
不存在的键 IsValid: false

3、通过类型信息创建实例

var a int
myType := reflect.TypeOf(a)
myIns := reflect.New(myType)	//	*int
myIns.Elem().SetInt(15)
fmt.Println(myIns.Type())
fmt.Println(myIns.Elem().Interface())

//	输出
// *int
//	15

4、通过反射调用函数

// 普通函数
func add(a, b int) int {
    return a + b
}

func main() {
    // 将函数包装为反射值对象
    funcValue := reflect.ValueOf(add)
    // 构造函数参数, 传入两个整型值
    paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
    // 反射调用函数
    retList := funcValue.Call(paramList)
    // 获取第一个返回值, 取整数值
    fmt.Println(retList[0].Int())
}

//	调用成功后,通过 retList[0] 取返回值的第一个参数,使用 Int 取返回值的整数值。
//	Call(in []Value) []Value



三、反射三定律

1、反射可以将"接口类型变量"转换为"反射类型对象"。

注:这里反射类型指 reflect.Typereflect.Value

TypeOfValueOf的参数是空接口类型,任何变量都可以传入。

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

2、反射可以将"反射类型对象"转换为"接口类型变量"。

reflect.Type本身是接口,所以传给fmt的函数会打印底层数据。reflect.Valueinterface()方法。

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

var a int
fmt.Println(reflect.ValueOf(a).Interface())

3、如果要修改反射类型对象其值必须是可写的

  Go语言中类似 xx.f[1]*p 形式的表达式都可以表示变量,但是其它如x + 1f(2) 则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

  可写性最终是由一个反射对象是否存储了原始值而决定的。对于一个不具有可写性Value类型变量,调用 Set方法会报出错误。我们可以通过CanSet()方法检查一个 reflect.Value 类型变量的可写性CanAddr()表示可寻址,可寻址的Value不一定可写,还需要判断是否可导出。比如,一个结构体中的私有成员就是不可导出的(unexported field)。

示例:

var a int = 3
fmt.Printf("a CanAddr: %v\n", reflect.ValueOf(a).CanAddr())

v := reflect.ValueOf(&a).Elem()
fmt.Printf("v CanAddr: %v\n", v.CanAddr())
fmt.Printf("v CanSet: %v\n", v.CanSet())

v.SetInt(1)
fmt.Println(a)

输出:

a CanAddr: false
v CanAddr: true
v CanSet: true
1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值