您好,欢迎来到年旅网。
搜索
您的当前位置:首页Golang学习(三十三) 反射

Golang学习(三十三) 反射

来源:年旅网

一、基本介绍

 1、重要的函数

func TypeOf(i interface{}) Type

func ValueOf(i interface{}) Value

以上函数都是空接口类型,可以传入任意值,之后我们将得到一个type和Value类型变量

这里的type和Value都是反射包中定义的结构体,我们可以通过该结构体调用包下方法

 2、基本使用

package main

import (
	"fmt"
	"reflect"
)

func main(){
	fmt.Println("--------TypeOf--------")
	sum := 100
	test1 := reflect.TypeOf(sum)   //调用TypeOf函数接收一个值,返回声明好的Type结构体
	fmt.Println(test1.Name())  //通过调用结构体下的方法和字段获取数据
	fmt.Println(test1.Kind())




	fmt.Println("--------ValueOf--------")
	test2 := reflect.ValueOf(sum)   //和typeOf类似,不同的是返回的Value结构体
	fmt.Println(test2.Type())       //Value主要是用于处理数据相关的
	fmt.Println(test2.Int())


}

 返回

--------TypeOf--------
int
int
--------ValueOf--------
int
100

3、reflect.Value类型转换

变量、interface{} 和reflect.Value是可以相互转换的

我们一般会在程序中添加一个函数用于接收空接口的数据

案例

package main

import (
	"fmt"
	"reflect"
)


type Stu struct {
	Name string
}
func test(b interface{})  {
	rVal := reflect.ValueOf(b)   //将传入的变量转换为Value类型

	iVal := rVal.Interface()    //在实际使用中,我们可能需要将Value转换为普通类型
	                            //reflect.Value 结构体下有Interface()方法转换回空接口模式


	v := iVal.(Stu)   //我们将空接口类型通过类型断言转换回原来的类型
	fmt.Println(v)

}
func main(){
	test(Stu{Name: "123456"})    //调用
}

 如上,我们先将数据转换为Value结构体,然后再通过Interface方法转换为空接口

然后通过类型推导,将空接口类型转换回原始数据,以上就是类型转换的方法

 类型转换流程图

4、reflect.Value类型与其他变量交互

reflect.Value是一个结构体类型,我们将数据传入进去后因为类型不同无法使用

该结构体提供了很多的方法,直接转换成对应的数据类型进行交互

 比如常见的int、string、bool等等,需要什么类型我们就转换成什么类型

案例

package main

import (
	"fmt"
	"reflect"
)


func reflectTest01(b interface{}) {
	rVal := reflect.ValueOf(b)
	n1 := 2 + rVal.Int()             //只要基本符合运算要求就可以转换
	fmt.Println(n1)

}


func main(){

	var num int = 100
	reflectTest01(num)
}

 二、反射注意事项

1、reflect.Value.Kind 获取变量的类别,返回的是一个常量

Kind返回变量持有的值的分类,如果变量是Value零值,返回值为Invalid

kind 表示一个大的分类,比如你int8 int16 int32 int,到kind这里都是int

package main

import (
	"fmt"
	"reflect"
)

type Student struct{
	Name string
	Age int
}


func reflectTest02(b interface{}) {

	//方法1  通过type类型的kind方法获取
    rtype := reflect.TypeOf(b)
	fmt.Println(rtype.Kind())
	
	//方法2  通过Value结构体的kind方法获取
    rVal := reflect.ValueOf(b)
	fmt.Println(rVal.Kind())

}


func main(){
	stu := Student{
		Name : "tom",
		Age : 20,
	}


	reflectTest02(stu)
}

2、type和kind的区别

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

package main

import (
	"fmt"
	"reflect"
)

type Test struct {
	Name string
}

func reflectTest01(b interface{}) {
	rVal := reflect.ValueOf(b)

	fmt.Println(rVal.Type())
	fmt.Println(rVal.Kind())
}

func main(){

	var num int = 100
	reflectTest01(num)

	var test Test = Test{Name:"123"}
	reflectTest01(test)
}

 返回

int
int
main.Test
struct

如上,当传入的是int类型,kind和type是相同的

当传入的是结构体类型,type会找到是那个结构体被传入了,kind只会返回这是一个结构体

3、通过反射可以让变量、空接口、Value结构体相互进行转换(上面有案例)

4、通过反射修改变量

通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针来完成

package main

import (
	"fmt"
	"reflect"
)

func reflect01(b interface{}){
	rVal := reflect.ValueOf(b)
	fmt.Printf("%v",rVal.Kind())


	rVal.SetInt(20)   //Value中有大量的Set方法,可以修改不同的数据类型
}

func main(){
	var num int = 10
	reflect01(&num)  //当要在其他函数中修改传递的值需要传入内存地址
	fmt.Println(num)
}

 返回

ptr
panic: reflect: reflect.Value.SetInt using unaddressable value

但是我们不能直接*rVal这样无法转换,Value结构体下提供了一个Elem方法提供指针调用

package main

import (
	"fmt"
	"reflect"
)

func reflect01(b interface{}){

	rVal := reflect.ValueOf(b)
	fmt.Printf("%v\n",rVal.Kind())



	rVal.Elem().SetInt(20)  //这里的Elem简单可以理解为 *ptr 的意思
	                           //取到指针具体指向内存空间的值,然后在做SetInt 修改变量的操作
}

func main(){
	var num int = 10
	reflect01(&num)
	fmt.Println(num)
}

三、反射练习

练习1

给你一个变量 var v float = 1.2 
请使用反射得到他的reflect.Value,然后获取对应的type、kind和值
并将reflect.Value转换成interface{}   再将interface{}转换成float

代码

package main

import (
	"fmt"
	"reflect"
)

func reflect01(b interface{}){
	rtype := reflect.TypeOf(b)
	rVal := reflect.ValueOf(b)

	fmt.Println(rtype,rVal,rVal.Kind())

	iV := rVal.Interface()
	fmt.Println(iV.(float))
}

func main(){
	var v float = 1.2
	reflect01(v)  
	fmt.Println(v)
}

练习2 判断代码是否正确,为什么

package main

import (
	"fmt"
	"reflect"
)

func main(){
	var str string = "tom"   //ok
	fs := reflect.ValueOf(str)  //ok
	fs.SetString("jack")  //error  要求用指针去调用
	fmt.Printf("%v\n",str)
}

返回

panic: reflect: reflect.Value.SetString using unaddressable value
package main

import (
	"fmt"
	"reflect"
)

func main(){
	var str string = "tom"
	fs := reflect.ValueOf(&str)  //添加改为内存地址传入
	fs.Elem().SetString("jack")  //添加Elem为指定指针的值
	fmt.Printf("%v\n",str)
}

反射最佳实践1

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

1、调用结构体方法函数介绍

func (v Value) Method(i int) Value
                       //获取结构体下所有的方法名
                       //方法名以顺序对应i值, i默认等于0
                       //我们可以指定返回的方法是索引的第几个


func (v Value) Call(in []Value) []Value
                   //当我们找到上面的方法后
                   //通过Call去调用该方法

2、 编写结构体模板

package main

import (
	"fmt"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}


func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}


func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}


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 main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	fmt.Println(a)

}

3、添加反射 获取信息

这里添加反射获取 数据类型、类别  并判断值是否为结构体类别

package main

import (
	"fmt"
	"reflect"
)

type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}

func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}

func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}

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{}){
	val := reflect.ValueOf(a)

	Typ := val.Type()

	kd := val.Kind()            //通过Value获取到kind 类别

	if kd != reflect.Struct{
		fmt.Println("expect struct")
		return
	}
	fmt.Println("传入的值是结构体类型","结构体类型是"+ Typ.String())
}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}


	TestStruct(a)
}

4、获取结构体有几个字段,按照索引遍历整个结构体字段

package main

import (
	"fmt"
	"reflect"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}


func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}


func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}


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{}){
	val := reflect.ValueOf(a)


	num := val.NumField()    //NumField 方法上面说过,返回这个结构体有几个字段,以索引的形式

	fmt.Printf("struct has %d fields\n",num)  //我们上面定义了4个字段,返回应该是4



	for i := 0 ;i < num;i++ {
		fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))  //Field方法 用于查询对应索引位的字段名是什么

	}
}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	TestStruct(a)
}

5、获取结构体每个字段的名称 和字段的别名标签

package main

import (
	"fmt"
	"reflect"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}

func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}

func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}

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{}){
	typ := reflect.TypeOf(a)   //这里注释恢复一下  因为标签属于类型中的
	
	val := reflect.ValueOf(a)
	kd := val.Kind()

	if kd != reflect.Struct{
		fmt.Println("expect struct")
		return
	}

	num := val.NumField()
	fmt.Printf("struct has %d fields\n",num)


	for i := 0 ;i < num;i++ {
		fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))

		tagVal := typ.Field(i).Tag.Get("json")  //查询索引位的字段,Tag标签是否存在值
		                                             //请求key为json的tag 别名

		if tagVal != "" {  //如果该字段有标签,就显示,否则就不显示
			fmt.Printf("Field %d: tag为=%v\n",i,tagVal)
		}

	}


}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	TestStruct(a)
}

返回

struct has 4 fields

Field 0: 值为=黄鼠狼精
Field 1: 值为=400
Field 2: 值为=30.8
Field 3: 值为=xxx

Field 0: tag为=name
Field 1: tag为=monster_age    //其他没有配置json,所以没有别名

上面有一段Field(i).Tag.Get("json") 没说这个是那里蹦出来的

下面找一下,登陆官方文档找到reflect包

https://studygolang.com/pkgdoc

找到 type、Value结构体及方法

我们上面那个是走的typeOf的,返回的是type结构体,这里我们点type Type

在Type这个结构体下寻找 Field方法查询字段信息

 可以看到这个Field方法 返回的是一个 叫StructField的类型,我们来继续查一下

 可以看到返回的类型是一个结构体,这个结构体下有包含一些结构体的字段信息

我们上面要查询的是结构体的标签,所以选择了Tag字段,而该字段的类型是StructTag

这个字段是一个string类型,下面提供了如何去请求这个字段的方法

需要给Get传入我们要匹配的key是名称,我们上面都是json开头是

 6、获取结构体方法的数量

package main

import (
	"fmt"
	"reflect"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}


func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}


func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}


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{}){
	typ := reflect.TypeOf(a)
	val := reflect.ValueOf(a)
	kd := val.Kind()

	if kd != reflect.Struct{
		fmt.Println("expect struct")
		return
	}

	num := val.NumField()



	for i := 0 ;i < num;i++ {
		tagVal := typ.Field(i).Tag.Get("json")
		if tagVal != "" {
		}

	}



	numOfMethod := val.NumMethod()  //返回一共有多少个方法
	fmt.Printf("struct has %d methods\n",numOfMethod)


	val.Method(1).Call(nil) //Method调用第二个方法,这里是走索引0-2
	                              //Call 调用当前拿到的方法



}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	TestStruct(a)
}

返回

struct has 3 methods
---start---
{黄鼠狼精 400 30.8 xxx}
---end---

这里又有疑问了,我怎么知道那个索引对应的是那个方法?

上面不是用的索引位是第二位吗,为什么调用到了第一个的方法? 下面说明

 反射原则说明

Method方法是依据你定义方法的首字母ASCII码进行排序的

以上3个方法 GetSum、Print、Set,通过首字母的ASCII码排序后实际上的顺序

GetSum(0)、Print(1)、Set(2)因为Print是不需要传参的,所以给一个nil

7、如何给Value结构体传递参数

package main

import (
	"fmt"
	"reflect"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}


func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}


func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}


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{}){
	val := reflect.ValueOf(a)
	
	var params []reflect.Value   //定义一个type Value 类型的切片

	params = append(params,reflect.ValueOf(10))  //将要传递的值做常量加入到切片中
	params = append(params,reflect.ValueOf(40))



	res := val.Method(0).Call(params) //将整个切片利用Call提交出去,返回的值也是[]reflect.Value


	fmt.Println("res=",res[0].Int())  //因为此时res中的类型是[]reflect.Value
	                                      //他里面只有一个值,我们使用0索引为调用,并转换为int食醋胡

}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	TestStruct(a)
}

返回

res= 50

反射最佳实践2

使用反射的方式来"获取结构体的tag标签","遍历"字段的值

package main

import (
	"fmt"
	"reflect"
)


type Monster struct{
	Name string   `json:"name"`
	Age int       `json:"monster_age"`
	Score float32
	Sex string
}


func (s Monster) Print(){
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}


func (s Monster) GetSum(n1,n2 int) int {
	return n1 + n2
}


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{}){
	typ := reflect.TypeOf(a)
	val := reflect.ValueOf(a)
	kd := val.Kind()

	//下面我们传入了内存地址,这里对比是否是结构体要修改
	//要多一层判断,判断是否是指针,并且还要用elem去获取指针的信息(*ptr)
	//根据获取的数据再去通过kind去获取传入的类型是什么,进而去对比
	if kd != reflect.Ptr && val.Elem().Kind() ==  reflect.Struct{
		fmt.Println("expect struct")
		return
	}


	num := val.Elem().NumField()  	//因为是指针,我们这里获取字段的长度前面也要加个elem表示这个值是一个指针


	val.Elem().Field(0).SetString("liuwei")  	//我们通过setString去修改 索引位0的值,同样要加Elem


	for i := 0 ;i < num;i++ {
		fmt.Printf("Field %d: 值为=%v\n", i, val.Elem().Field(i))


		tagVal := typ.Elem().Field(i).Tag.Get("json")  //只要是获取这个指针的值信息的,都要带elem
		if tagVal != "" {
			//fmt.Printf("Field %d: tag为=%v\n",i,tagVal)
		}

	}

	tag := typ.Elem().Field(0).Tag.Get("json")
	fmt.Printf("tag=%s\n",tag)
	numOfMethod := val.Elem().NumMethod()

	fmt.Printf("struct has %d methods",numOfMethod)
	val.Elem().Method(1).Call(nil)



}

func main(){
	a := Monster{
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.8,
		Sex: "xxx",
	}

	TestStruct(&a)   //传入内存地址
	fmt.Println(a)
}

 返回

Field 0: 值为=liuwei
Field 1: 值为=400
Field 2: 值为=30.8
Field 3: 值为=xxx
tag=name
struct has 3 methods---start---
{liuwei 400 30.8 xxx}
---end---
{liuwei 400 30.8 xxx}

反射最佳实践3   函数适配器

定义两个函数test1、test2  定义一个适配器函数,用做统一处理接口

package main

import (
	"fmt"
	"reflect"
)

func Call1(v1 int,v2 int){       //定义两个功能函数
	fmt.Println(v1 / v2)
}
func Call2(v1 int,v2 int,str string){
	fmt.Println(v1 + v2 ,str)
}



//定义一个通用的函数适配器
//通过args ...interface{} 用于接收多个参数
func bridge(call interface{},args ...interface{}) {

	n := len(args)                      //先统计传入的参数长度
	inValue := make([]reflect.Value,n)  //根据传入的参数数量 创建一个type Value的切片

	for i := 0; i < n;i++{                     //因为传入的是多个参数,接收到的是切片
		inValue[i] = reflect.ValueOf(args[i])   //循环将接收到的参数 以常量的形式加入到[]reflect.Value切片中

	}
	function := reflect.ValueOf(call)       //将传入的函数转换为Value结构体类型
	function.Call(inValue)     //使用Value Call方法直接执行传入的函数,并将[]Value的inValue 作为参数交给函数使用
}

func main()  {
	bridge(Call1,1,2)   //封装好后,直接输如函数加参数即可
	bridge(Call2,1,2,"test2")
}

反射最佳实践4

使用反射操作任意结构体类型

package main

import (
	"fmt"
	"reflect"
)

type user struct{
	Userld string
	Name string
}

func main(){
	model := &user{}
	sv := reflect.ValueOf(model)
	fmt.Println("reflect.ValueOf",sv.Kind().String())

	sv = sv.Elem()  //可以在这里做一下 指针指定,下面就不用指定了
	fmt.Println("reflect.ValueOf.Elem",sv.Kind().String())


	//FieldByName指定要修改的字段
	//SetString 修改字段信息
	sv.FieldByName("Userld").SetString("12345678")
	sv.FieldByName("Name").SetString("nickname")
	fmt.Println("model",model)
}

反射最佳实践5 

package main

import (
	"fmt"
	"reflect"
)

type user struct{
	Userld string
	Name string
}

func main(){

	var (
		model *user
		st reflect.Type
		elem reflect.Value
	)

	st = reflect.TypeOf(model)
	fmt.Println("reflect.TypeOf",st.Kind().String()) //ptr

	st = st.Elem() //这里指定一下指针,下面就不用指定elem
	fmt.Println("reflect.TypeOf.Elem",st.Kind().String()) //struct



	//定义一个结构体
	elem =  reflect.New(st)  //这里的New返回一个Value类型值,该值持有一个指向类型为st的新申请的零值的指针,
	                         // 返回值的Type为PtrTo(st)。

	fmt.Println("reflect.New",elem.Kind().String())  //ptr
	fmt.Println("reflect.New.Elem",elem.Elem().Kind().String())  //Struct

	//可以看到我们New出来的Value类型和上面提前定义的结构体类似



	model = elem.Interface().(*user)   //这里通过转换位空接口后 类型推导为*user的结构体类型
	                                   //并将我们声明出来的这个结构体又赋予给了开头的model结构体变量
	                                   //但是我们现在这个elem是一个内存地址啊,赋予到了model那么elem就和model数据同步了


	elem = elem.Elem()        //这里通过Elem这里将内存地址进行调用,类似*ptr
                              //model现在拿的是elem给的值,我们还可以通过elem去动态修改其他值
	                          //当model或elem被修改时,对方也会同步数据

	elem.FieldByName("Userld").SetString("12345678")  //通过修改elem会影响到model变量
	elem.FieldByName("Name").SetString("nickname")
	fmt.Println("model model.name",model,model.Name)

}

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- oldu.cn 版权所有 浙ICP备2024123271号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务