GO语言reflect反射使用

本文详细讲解了Go语言中Reflect包的使用,包括Value和Type的基本概念,如何通过反射转换、访问和修改值,结构体的字段遍历、值修改、方法调用,以及与其他类型的交互。

GO语言reflect反射使用


一、介绍

​ 反射是 Go 语言比较重要的特性。虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制实现简化代码的逻辑。因为 Go 语言的语法元素很少、设计简单,所以它没有特别强的表达能力,但是 Go 语言的 reflect 包能够弥补它在语法上的一些劣势。reflect实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,分别是:

1、Value
func ValueOf(i interface{}) Value

ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。

type Value struct {
    // 内含隐藏或非导出字段
}

Value为go值提供了反射接口。

不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。

Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回"<invalid Value>",所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。

2、Type
func TypeOf(i interface{}) Type

TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。

type Type interface {
    // Kind返回该接口的具体分类
    Kind() Kind
    // Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
    Name() string
    // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
    PkgPath() string
    // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
    // 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
    String() string
    // 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
    Size() uintptr
    // 返回当从内存中申请一个该类型值时,会对齐的字节数
    Align() int
    // 返回当该类型作为结构体的字段时,会对齐的字节数
    FieldAlign() int
    // 如果该类型实现了u代表的接口,会返回真
    Implements(u Type) bool
    // 如果该类型的值可以直接赋值给u代表的类型,返回真
    AssignableTo(u Type) bool
    // 如该类型的值可以转换为u代表的类型,返回真
    ConvertibleTo(u Type) bool
    // 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
    Bits() int
    // 返回array类型的长度,如非数组类型将panic
    Len() int
    // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
    Elem() Type
    // 返回map类型的键的类型。如非映射类型将panic
    Key() Type
    // 返回一个channel类型的方向,如非通道类型将会panic
    ChanDir() ChanDir
    // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
    NumField() int
    // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
    Field(i int) StructField
    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
    // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
    // 如非函数类型将panic
    IsVariadic() bool
    // 返回func类型的参数个数,如果不是函数,将会panic
    NumIn() int
    // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
    In(i int) Type
    // 返回func类型的返回值个数,如果不是函数,将会panic
    NumOut() int
    // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
    Out(i int) Type
    // 返回该类型的方法集中方法的数目
    // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
    // 匿名字段导致的歧义方法会滤除
    NumMethod() int
    // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    Method(int) Method
    // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    MethodByName(string) (Method, bool)
    // 内含隐藏或非导出方法
}

Type类型用来表示一个go类型。

不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。

reflect.Typereflect.Value都具有Kind方法,可以通过Kind方法验证反射类型是否相同。

	type A struct {
		Id   int `json:"id"`
		Name string
	}
	num := 10.001
	a1 := A{}
	a2 := A{}
	fmt.Println(reflect.ValueOf(num).Kind() == reflect.Float64)
	fmt.Println(reflect.ValueOf(a1).Kind() == reflect.ValueOf(a2).Kind())
	// output:
	// true
	// true

二、基本使用

1、反射转换为接口

reflect.Value中的Interface方法以空接口的形式返回reflect.Value中的值,如果进一步获取空接口的值,可以通过接口的类型断言对接口的值进行转换。下例实现了从值到反射,在从反射到值的过程。

	var num float64 = 10.001
	prtNum := reflect.ValueOf(&num)
	value := reflect.ValueOf(num)
	p := prtNum.Interface().(*float64)
	v := value.Interface().(float64)
	fmt.Println(*p, v)
	// output:10.001 10.001

除了使用接口,reflect.Value还提供了一些转换到具体类型的方法。

func (v Value) String() string
func (v Value) Bytes() []byte
func (v Value) Float() float64
func (v Value) Int() int64
...

Int()返回v持有的有符号整数(表示为int64),如果v的值不管是Int、Int8、Int16、Int32还是Int64,都将会返回int64。要注意如果转换的类型与实际类型不符,回在运行时报错。

对于一些不清楚是不是指针类型的值,可以使用reflect.Indirect方法进行转换。

	var num float64 = 10.001
	prtNum := reflect.ValueOf(&num)
	indirectVal := reflect.Indirect(prtNum)
	value := reflect.ValueOf(num)
	p := prtNum.Interface().(*float64)
	v := value.Interface().(float64)
	iv := indirectVal.Interface().(float64)
	fmt.Println(p, v, iv)
	// output:0xc0000aa058 10.001 10.001

	// Indirect returns the value that v points to.
	// If v is a nil pointer, Indirect returns a zero Value.
	// If v is not a pointer, Indirect returns v.
	// func Indirect(v Value) Value 
2、Elem()间接访问

如果反射中存储的是指针或接口,可以通过Elem方法返回指向的数据。

	var num float64 = 10.001
	var num2 interface{}
	num2 = &num
	n := reflect.ValueOf(&num).Elem().Float()
	n2 := reflect.ValueOf(num2).Elem().Float()
	fmt.Println(n, n2)
	// output:10.001 10.001

如果接口指向的不是指针,会报错。

	var num float64 = 10.001
	var num2 interface{}
	num2 = num
	n2 := reflect.ValueOf(num2).Elem().Float()
	fmt.Println(n2)
	/*
	panic: reflect: call of reflect.Value.Elem on float64 Value
	goroutine 1 [running]:
	reflect.Value.Elem({0x4c40a0, 0xc0000aa058, 0xc0000cbf70})
        D:/Go/src/reflect/value.go:1191 +0x15a
	main.main()
        G:/Go/src/a/main.go:35 +0xac
	exit status 2
	*/
3、修改反射的值

reflect.Value提供了Set方法可以修改反射中存储的值。

// Set assigns x to the value v.
// It panics if CanSet returns false.
// As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value) 

该方法的参数仍然是reflect.Value类型,单要求反射中的类型必须是指针,否则会报错。为了避免这种问题,reflect.Value提供了CanSet方法用于获取当前反射值是否能赋值。

	var num float64 = 10.001
	v := reflect.ValueOf(num)
	fmt.Println(v.CanSet())
	// output:false
	var num float64 = 10.001
	v := reflect.ValueOf(&num).Elem()
	fmt.Println(v.CanSet())
	v.Set(reflect.ValueOf(9.99))//重新赋值
	fmt.Println(v.Float())
	// output:
	//true
	//9.99

三、结构体反射

1、遍历结构体字段
package main

import (
	"fmt"
	"reflect"
)

type A struct {
	Age  int `json:"age"`
	Name string
    id   int
}
type Res struct {
	Result string
}
type Req struct {
	Result string
}

func (a A) Add(n int) (int, string) {
	return a.Age + n, a.Name
}
func (a A) Say(str string) {
	fmt.Println(a.Name + str)
}
func (a *A) Run(req Req, res *Res) error {
	res.Result += req.Result
	return nil
}

func main() {
	a := A{10, "tom", 10001}
	v := reflect.ValueOf(a)
	t := reflect.TypeOf(a)
	for i := 0; i < t.NumField(); i++ {
		fmt.Println(v.Field(i), t.Field(i))
	}
}

//output:
//10 {Age  int json:"age" 0 [0] false}
//tom {Name  string  8 [1] false}

通过reflect.TypeNumField方法获取结构体中的字段个数,并通过Field方法获取结构体的元信息,返回值为StructField类型。所以可以通过t.Field(i).Tag.Get("json")的方法获取结构体字段的tag属性。

type StructField struct {
	   	// Name is the field name.
	   	Name string

	   	// PkgPath is the package path that qualifies a lower case (unexported)
	   	// field name. It is empty for upper case (exported) field names.
	   	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	   	PkgPath string

	   	Type      Type      // field type
	   	Tag       StructTag // field tag string
	   	Offset    uintptr   // offset within struct, in bytes
	   	Index     []int     // index sequence for Type.FieldByIndex
	   	Anonymous bool      // is an embedded field
}

reflect.ValueField方法可以获取结构体字段的值。

2、修改结构体的值
	a := A{10, "tom", 10001}
	v := reflect.ValueOf(&a).Elem()
	if v.Field(0).CanSet() {
		v.Field(0).Set(reflect.ValueOf(18))
		fmt.Println(v.Field(0))
	}
	fmt.Println(v.Field(1).CanSet())
	fmt.Println(v.Field(2).CanSet())
	//output:
	//18
	//true
	//false

通过上面代码可以看出,可导出字段的值是可以被修改的,不可导出字段的值是不能修改的。

3、遍历结构体的方法
package main

import (
	"fmt"
	"reflect"
)

type A struct {
	Age  int `json:"age"`
	Name string
	id   int
}
type Res struct {
	Result string
}
type Req struct {
	Result string
}

func (a A) Add(n int) (int, string) {
	return a.Age + n, a.Name
}
func (a A) Say(str string) {
	fmt.Println(a.Name + str)
}
func (a *A) Run(req Req, res *Res) error {
	res.Result += req.Result
	return nil
}

func main() {
	a := A{10, "tom", 10001}
	t := reflect.TypeOf(a)
	// 打印a对象的类名
	fmt.Println(t.Name())
	// 遍历对象绑定的方法
	num := t.NumMethod()
	for i := 0; i < num; i++ {
		m := t.Method(i)
		fmt.Println(m, m.Name, m.Type.NumIn(), m.Type.NumOut())
	}
	print("-------------------------\n")
	ptr := reflect.TypeOf(&a)
	fmt.Println(ptr.Name())
	num1 := ptr.NumMethod()
	for i := 0; i < num1; i++ {
		m1 := ptr.Method(i)
		fmt.Println(m1, m1.Name, m1.Type.NumIn(), m1.Type.NumOut())
	}
	print("-------------------------\n")
	t2 := reflect.PtrTo(reflect.TypeOf(a))
	fmt.Println(t2.Name())
	num2 := t2.NumMethod()
	for i := 0; i < num2; i++ {
		m := t2.Method(i)
		fmt.Println(m, m.Name, m.Type.NumIn(), m.Type.NumOut())
	}
}

//output:
/*
A
{Add  func(main.A, int) (int, string) <func(main.A, int) (int, string) Value> 0} Add 2 2
{Say  func(main.A, string) <func(main.A, string) Value> 1} Say 2 0
-------------------------

{Add  func(*main.A, int) (int, string) <func(*main.A, int) (int, string)Value> 0} Add 2 2
{Run  func(*main.A, main.Req, *main.Res) error <func(*main.A, main.Req, *main.Res) error Value> 1} Run 3 1     
{Say  func(*main.A, string) <func(*main.A, string) Value> 2} Say 2 0
-------------------------

{Add  func(*main.A, int) (int, string) <func(*main.A, int) (int, string)Value> 0} Add 2 2
{Run  func(*main.A, main.Req, *main.Res) error <func(*main.A, main.Req,*main.Res) error Value> 1} Run 3 1     
{Say  func(*main.A, string) <func(*main.A, string) Value> 2} Say 2 0
*/

reflect.TypeName方法可以返回类的名字,但是当传入的是指针时,会打印空值。所以,一般打印类名使用reflect.Indirect(reflect.ValueOf(a)).Type().Name()的方式打印类名。

reflect.TypeNumMethod方法可以返回类方法的数量,并通过Method方法获取该方法对应的Method的结构体。

// Method represents a single method.
type Method struct {
	// Name is the method name.
	Name string

	// PkgPath is the package path that qualifies a lower case (unexported)
	// method name. It is empty for upper case (exported) method names.
	// The combination of PkgPath and Name uniquely identifies a method
	// in a method set.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	PkgPath string

	Type  Type  // method type
	Func  Value // func with receiver as first argument
	Index int   // index for Type.Method
}

然后再通过m.Type.NumIn() m.Type.NumOut()获得该方法的输入参数的个数和输出参数的个数。并通过m.Type.Inm.Type.Out获取参数类型

	for j := 0; j < m.Type.NumIn(); j++ {
		fmt.Println(m.Type.In(j))
	}
	for j := 0; j < m.Type.NumOut(); j++ {
		fmt.Println(m.Type.Out(j))
	}
4、结构体方法的调用
func (v Value) Call(in []Value) []Value

Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果vKind不是Funcpanic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。如果v持有值是可变参数函数,Call方法会自行创建一个代表可变参数的切片,将对应可变参数的值都拷贝到里面。

out := reflect.ValueOf(a).Method(0).Call([]reflect.Value{reflect.ValueOf(10)})
fmt.Println(out[0].Int(), out[1].String())
//output:20 tom

四、反射与其他类型

对于其他类型可以通过XXXOf方法构造特定的reflect.Type类型。

	reflect.ArrayOf()
	reflect.ChanOf()
	reflect.FuncOf()
	reflect.MapOf()
	reflect.StructOf()
	reflect.SliceOf()
	reflect.PtrTo()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值