golang 反射机制

转载参考地址如下
Go语言反射reflect
Golang的反射reflect深入理解和示例

反射

  • 反射是指程序运行期对程序本身进行访问和修改的能力。
  • 即能够自描述和自控制。
  • 也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
  • 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Golang 反射reflect

  • go 程序在运行期使用reflect包访问程序的反射信息。
  • reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。

通过反射获取类型信息 reflect.TypeOf()

  • 使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
func main()  {
	var i int64=4
	var stu Student
	num:=float32(4)

	//# (0x10ee620,0x10ac540)
	fmt.Println(reflect.TypeOf(i))

	//获取数据类型
	// 方法一
	fmt.Println(reflect.TypeOf(i).Name())
	//方法二
	fmt.Println(reflect.TypeOf(num).String())
	//方法三
	fmt.Println(fmt.Sprintf("%T", num))
	fmt.Println(reflect.ValueOf(num))

	//比较区别
	typeOfStu1:=reflect.TypeOf(i)
	fmt.Println(typeOfStu1.Name(),typeOfStu1.Kind(),typeOfStu1.String())

	typeOfStu:=reflect.TypeOf(stu)
	fmt.Println(typeOfStu.Name(),typeOfStu.Kind(),typeOfStu.String())


	
}
int64
int64
float32
float32
4
int64 int64 int64
Student struct main.Student

理解反射的类型(Type)与种类(Kind)

  • Type 指的是系统原声数据类型,如int,string,bool,float32等(返回string)
  • Kind 指的是对象归属的品种(返回Kind)
Name() string
String() string

// Kind returns the specific kind of this type.
Kind() Kind

reflect 包中KINd定义

// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

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.Elem()通过反射获取指针指向的元素内容

  • 这个获取过程被称为取元素,等效于对指针做了一个*操作

通过反射获取结构体的成员类型

通过反射获取值信息

  • reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。

func main() {

	//声明整型变量a并赋初值
	var a int = 1024

	//获取变量a的反射值对象
	valueOfA := reflect.ValueOf(a)

	//获取interface{}类型的值,通过类型断言转换
	var getA int = valueOfA.Interface().(int)

	//获取64位的值,强制类型转换为int类型
	var getB int = int(valueOfA.Int())

	fmt.Println(valueOfA)

	fmt.Println(getA, getB)
}
1024
1024 1024

通过反射访问结构体成员的值

反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。

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

下面代码构造一个结构体包含不同类型的成员。通过 reflect.Value 提供的成员访问函数,可以获得结构体值的各种数据。

反射访问结构体成员的值:

package main

import (
"fmt"
"reflect"
)

//定义结构体
type Student struct {
	Name string
	Age  int

	//嵌入字段
	float32
	bool


	next *Student
}

func main() {

	//值包装结构体
	rValue := reflect.ValueOf(Student{
		next: &Student{},
	})

	//获取字段数量
	fmt.Println("NumField:", rValue.NumField())

	//获取索引为2的字段(float32字段)
	//注:经过测试发现Field(i)的参数索引是从0开始的,
	//并且是按照定义的结构体的顺序来的,而不是按照字段名字的ASCii码值来的
	floatField := rValue.Field(2)

	//输出字段类型
	fmt.Println("Field:", floatField.Type())

	//根据名字查找字段
	fmt.Println("FieldByName(\"Age\").Type:", rValue.FieldByName("Age").Type())

	//根据索引查找值中next字段的int字段的值
	fmt.Println("FieldByIndex([]int{4, 0}).Type()", rValue.FieldByIndex([]int{4, 0}).Type())

}

NumField: 5
Field: float32
FieldByName("Age").Type: int
FieldByIndex([]int{4, 0}).Type() string


判断值的空和有效性

方法说明
IsNil() bool返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。

通过反射修改变量的值

使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。

  • 判断及获取元素的相关方法
    使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
方法名备注
Elem() Value取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value
Addr() Value对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
CanAddr() bool表示值是否可寻址
CanSet() bool返回值能否被修改。要求值可寻址且是导出的字段
  • 值修改相关方法
Set(x Value)将值设置为传入的反射值对象的值
Setlnt(x int64) 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64)使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64)使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool)使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte)设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string)设置字符串值。当值的类型不是 string 时会发生宕机

通过类型信息创建实例

通过反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

func main() {

	//将函数包装为反射值对象
	funcValue:=reflect.ValueOf(add)

	//构造函数参数,传入两个整形值
	paramList:=[]reflect.Value{reflect.ValueOf(2),reflect.ValueOf(3)}


	//反射调用函数
	retList:=funcValue.Call(paramList)

	fmt.Println(retList[0])

	
	//attention 
	for k,v:=range retList{
		fmt.Println(k,v)
	}
	fmt.Println(reflect.TypeOf(retList).Kind()) 


}

5
0 5
1 6
slice


提示

反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。

通过发射调用方法

调用方法和调用函数是一样的,只不过结构体需要先通过rValue.Method()先获取方法再调用,请看如下示例:

func main() {

	var myMath=MyMath{Pi:3.1415926}


	//获取myMath 的值对象
	rValue:=reflect.ValueOf(myMath)

	//获取该结构体有多少个方法
	numOfMethod:=rValue.NumMethod()
	fmt.Println(numOfMethod)

	//构造函数参数,传入两个整形值
	paramList:=[]reflect.Value{reflect.ValueOf(4),reflect.ValueOf(7)}


	//调用结构体的第一个方法Method(0)
	//注意:在反射值对象中方法索引的顺序并不是结构体方法定义的先后顺序
	//而是根据方法的ASCII码值来从小到大排序,所以Dec排在第一个,也就是Method(0)
	result := rValue.Method(0).Call(paramList)

	fmt.Println(result[0].Int())


}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值