go语言之反射 ****

反射的引入

使用反射机制,编写函数的适配器

基本介绍

1)反射可以在运行时动态获取变量的各种信息,比如变量类型,类别

2)如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)

3)通过反射,可以修改变量的值,可以调用关联方法

4)使用反射,需要import (“reflect”)

package reflect

import "reflect"

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

type Type

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)
    // 内含隐藏或非导出方法
}

func TypeOf
func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。

type Value

type Value struct {
    // 内含隐藏或非导出字段
}
Value为go值提供了反射接口。

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

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

如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。
func ValueOf(i interface{}) Value
ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。

  反射示意图

image

反射的应用场景

反射常见的应用常见有

1)不知道接口调哪个函数,根据传入参数在运行时确定调用的具体接口,这需要对函数或方法反射 

image

第一个参数funcPrt以接口的形式传入函数指针,函数参数args以可变的形参式传入,bridge 函数中可以用反射来动态执行funcPtr函数

2)对结构的序列化时,如果结构体有指定tag,也会使用到反射生成对应字符串

反射重要的函数和概念

1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型

2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型reflect.Value 是一个结构类型。通过reflect.Value,可以获取到关于该变量很多信息

3)变量、interface{}和reflect.Value 是可以相互转换的,这点在开发中经常使用。示意图

图1

image

 图2

image

 

 

func (Value) Interface

func (Value) Interface
func (v Value) Interface() (i interface{})
本方法返回v当前持有的值(表示为/保管在interface{}类型),等价于:

var i interface{} = (v's underlying value)
如果v是通过访问非导出结构体字段获取的,会导致panic。

入门

package main

import (
	"fmt"
	"reflect"
)

//反射
func receiveTest01(b interface{}){
	//通过反射获取到传入的变量类型,以及类别,值是什么
	//先获取类型
	rType :=reflect.TypeOf(b)//
	fmt.Println(rType)
	fmt.Println(rType.Name())
	//获取vakue
	rvlue := reflect.ValueOf(b)
	fmt.Printf("revlue=%v type=%T\n",rvlue,rvlue)
	//取值并计算
	n2 := 2+rvlue.Int()
	fmt.Println(n2)
	//下面将rvlue转成接口
	c := rvlue.Interface()
	//接口通过断言转回int
	d := c.(int)
	fmt.Printf("c=%v type=%T\n",d,d)


}
func main(){
	//定义int 类型
	var num int = 100
	receiveTest01(num)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter18\reflectdemo01\main.go
// int
// int
// revlue=100 type=reflect.Value
// 102
// c=100 type=int

  结构体示例

package main

import (
	"fmt"
	"reflect"
)

//反射
func receiveTest01(b interface{}){
	//通过反射获取到传入的变量类型,以及类别,值是什么
	//先获取类型
	rType :=reflect.TypeOf(b)//
	fmt.Println(rType)
	fmt.Println(rType.Name())
	//获取vakue
	rvlue := reflect.ValueOf(b)
	fmt.Printf("revlue=%v type=%T\n",rvlue,rvlue)
	//取值并计算
	n2 := 2+rvlue.Int()
	fmt.Println(n2)
	//下面将rvlue转成接口
	c := rvlue.Interface()
	//接口通过断言转回int
	d := c.(int)
	fmt.Printf("c=%v type=%T\n",d,d)
}
type  Student struct{
	Name string
	Age  int
}
func receiveTest02(b interface{}){
	rType :=reflect.TypeOf(b)
	fmt.Println("类型",rType)
	rVal := reflect.ValueOf(b)
	fmt.Printf("revlue=%v type=%T\n",rVal,rVal)
	//转为
	x := rVal.Interface()
	//将接口转成通过断言需要判断的类型
	//通过Switch 的断言形式做的更加灵活
	d ,ok:= x.(Student)
	if ok{
		fmt.Println("名字",d.Name)
	}

}

func main(){
	//定义int 类型
	// var num int = 100
	// receiveTest01(num)
	var t Student = Student{"小鬼",8}
	receiveTest02(t)
}
//执行结果
// 类型 main.Student
// revlue={小鬼 8} type=reflect.Value
// 名字 小鬼

  注意事项跟联系

1)reflect.Value.kind 获取变量的类别,返回一个常量

2)type 是类型,kind是类别,type和kind 可能相同的,也可能是不同的

例如: var num int= 10 num 的type 是int ,kind也是int

例如 var stu Student stu 的type是 包名.Student, kind 是struct

示例

package main

import (
	"fmt"
	"reflect"
)

//反射
func receiveTest01(b interface{}){
	//通过反射获取到传入的变量类型,以及类别,值是什么
	//先获取类型
	rType :=reflect.TypeOf(b)//
	fmt.Println(rType)
	fmt.Println(rType.Name())
	//获取vakue
	rvlue := reflect.ValueOf(b)
	fmt.Printf("revlue=%v type=%T\n",rvlue,rvlue)
	//取值并计算
	n2 := 2+rvlue.Int()
	fmt.Println(n2)
	//下面将rvlue转成接口
	c := rvlue.Interface()
	//接口通过断言转回int
	d := c.(int)
	fmt.Printf("c=%v type=%T\n",d,d)
}
type  Student struct{
	Name string
	Age  int
}
func receiveTest02(b interface{}){
	rType :=reflect.TypeOf(b)
	fmt.Println("类型",rType)
	rVal := reflect.ValueOf(b)
	//获取变量对应的kind
	//rVal.Kind()===>
	//rType.Kind()===>
	fmt.Printf("kind1=%v ****\n",rType.Kind())
	fmt.Printf("kind2=%v 55\n",rVal.Kind())

	fmt.Printf("revlue=%v type=%T\n",rVal,rVal)
	//转为
	x := rVal.Interface()
	//将接口转成通过断言需要判断的类型
	//通过Switch 的断言形式做的更加灵活
	d ,ok:= x.(Student)
	if ok{
		fmt.Println("名字",d.Name)
	}

}

func main(){
	//定义int 类型
	// var num int = 100
	// receiveTest01(num)
	var t Student = Student{"小鬼",8}
	receiveTest02(t)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter18\reflectdemo01\main.go
// 类型 main.Student
// kind=struct ****
// kind=struct 55
// revlue={小鬼 8} type=reflect.Value
// 名字 小鬼

  3)通过反射可以让变量在interface{}和Reflect.Value 之间互相转换

4)使用反射的方式获取变量的值(并返回对应类型),要求数据类型匹配,例如x是int那么久应该使用reflect.Value(x).Int(),而不能使用其他的,否则报panic

5)通过反射来修改变量的值,注意应该使用Setxxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量值,同时需要使用到,reflect.Value.Elem()

6) reflect.Value.Elem()

func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

  示例修改int 值

package main

import (
	//"fmt"
	"fmt"
	"reflect"
)

//通过反射修改
// num int 的值,修改student的值

func reflect01(b interface{}){
	rVal:= reflect.ValueOf(b)
	fmt.Printf("kind=%v\n",rVal.Kind())//地址值
	//rVal.SetInt(20)
	//*&rVal.SetInt(20)
	/*
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
	*/
	rVal.Elem().SetInt(20)


}
func main(){
	var num int = 10
	//fmt.Println()
	fmt.Println("num=",num)
	reflect01(&num)
	fmt.Println("num=",num)
}
//执行结构
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter18\reflectdemo02\main.go
// num= 10
// kind=ptr
// num= 20

  练习

package main

import (
	//"fmt"
	"fmt"
	"reflect"
)
func reflect01(b interface{}){
	c := reflect.ValueOf(b)
	d := reflect.TypeOf(b)
	fmt.Printf("c的kind=%v\n",c.Kind())
	fmt.Printf("c的type=%v\n",d)
	fmt.Println("c=",c)	
	r := c.Interface()
	a := r.(float64)
	fmt.Println(a)

}
func main(){
	var c float64= 90.6
	reflect01(c)
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter18\reflectdemo03\main.go
// c的kind=float64
// c的type=float64
// 90.6

  示例2

package main

import (
	"fmt"
	"reflect"
)

func main(){
	str := "cdf"
	fs :=reflect.ValueOf(&str)//要修改string值要传地址值,因为string是值类型
	fs.Elem().SetString("你好")//通过地址值取到原来值后并修改原来值
	fmt.Println("修改后str=",str)//打印str 变量查看修改
}
// 执行结果
// 修改后str= 你好

  反射实践

使用反射变量结构体字段

1)使用反射来遍历结构体的字段,用结构体的方法。并获取结构体标签值

func (Value) Method  默认按照方法名排序对应i值i从0开始

func (v Value) Method(i int) Value
返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。

func (Value) Call  传入参数和返回参数[]reflect.Value

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

  示意

package main

import (
	//"fmt"
	"fmt"
	"reflect"
)
//结构体
type Monster struct{
	Name string `json:"name"`
	Age int `json:"int"`
	Score float32
	sex string
}
//绑定方法
func (s Monster)Print(){
	fmt.Println("--------start-------")
	fmt.Println(s)
	fmt.Println("--------end---------")
}
//绑定方法返回两数和
func (s Monster)GgeSum(n1,n2 int) int{
	return n1+n2
}
//接收4个值给结构体变量赋值
func (s Monster)Set(name string,age int,score float32,sex string){
	s.Name=name
	s.Age=age
	s.Score=score
	s.sex=sex
}

func TestStruct(a interface{}){
	//获取reflect.Type 类型
	tye := reflect.TypeOf(a)
	//获取reflect.Value 类型
	val := reflect.ValueOf(a)
	//获取类别
	kb := val.Kind()
	//如果传入的是不是结构体,如果不是结构体退出
	if kb != reflect.Struct{
		fmt.Println("expect struct")
		return
	}
//获取结构体几个字段
	num := val.NumField()//返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
	fmt.Printf("struct has %d fields\n",num)
	//遍历结构体字段
	for i :=0;i<num;i++{
		fmt.Printf("Field %d:值为=%v\n",i, val.Field(i))//func (Value) Field  ;func (v Value) Field(i int) Value;返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
		//获取到结构体标签,注意需要通过reflect.type来获取; Field(i int) StructField
   		 // 返回索引序列指定的嵌套字段的类型,
    	// 等价于用索引中每个值链式调用本方法,如非结构体将会panic;func (StructTag) Get
		//func (tag StructTag) Get(key string) string
		//Get方法返回标签字符串中键key对应的值。如果标签中没有该键,会返回""。如果标签不符合标准格式,Get的返回值是不确定的。//func (tag StructTag) Get(key string) string
		tagVal := tye.Field(i).Tag.Get("json") 
		if tagVal !=""{
			fmt.Printf("Field %d: tag为=%v\n",i,tagVal)
		}
	}

	numOfMethod := val.NumMethod()//获取多少个方法
	fmt.Printf("struct has %d methods\n",numOfMethod)

	val.Method(1).Call(nil)// val.Method(1)从0开始获取第二个.Call(nil)调用方法,排序是按照方法首字母AISSC进行排序的,所以调用的是Print
	//调用结构体的第一个方法Method(0)
	var params []reflect.Value //声明切片
	params= append(params, reflect.ValueOf(10))
	params= append(params, reflect.ValueOf(40))
	res:=val.Method(0).Call(params)//传入参数是[]reflect.Value
	fmt.Println("res=",res[0].Int())//返回结果,返回结果是[]reflect.Value
}
func main(){
	var a Monster = Monster{
		Name: "哪吒",
		Age: 5,
		Score: 65.9,
		sex: "跑",
	}
	TestStruct(a)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter18\reflectdemo04\main.go
// struct has 4 fields
// Field 0:值为=哪吒
// Field 0: tag为=name
// Field 1:值为=5
// Field 1: tag为=int
// Field 2:值为=65.9
// Field 3:值为=跑
// struct has 3 methods
// --------start-------
// {哪吒 5 65.9 跑}
// --------end---------
// res= 50

  

 

 

 

 

 

 

 

 

 

  

posted @ 2026-03-28 13:42  烟雨楼台,行云流水  阅读(4)  评论(0)    收藏  举报