GO语言-反射reflect

作者: adm 分类: go 发布时间: 2023-11-02

Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法。但是在编译时并不直到这些变量的具体类型。这称为反射机制。

什么情况需要反射?
需要反射的2个常见场景:

不能明确函数传入的参数的类型,需要在运行时对传入的具体参数进行类型判断。
不确定接口调用哪些方法,需要在运行时根据传入参数确定。
为什么不建议用反射?
反射是一把双刃剑,虽然它的功能很强大。

反射相关的代码,一般是难以阅读的。
GO语言作为一门静态语言,编译器能在编码过程中提前发现一些类型错误。但是对于反射代码是无能为力的,所以包含反射相关的代码,往往运行一段时间以后才会发现错误。这往往会导致比较严重的后果。
反射对于性能影响是比较大的,比正常代码运行速度慢一到两个数量级。出于运行效率来讲,尽量避免使用反射。
反射如何实现的?
Go语言中我们学习过interface,他是Go语言实现抽象的一个非常强大的工具。当给接口变量赋予一个实体类型的时候,接口会存储实体类型的信息。反射就是通过接口的类型信息实现的,反射建立在类型的基础之上。

Go语言在reflect包中定义了各种类型,实现了反射的各种函数。通过它们可以在运行时检测类型的信息、改变类型的值。

回顾两个知识点:

go语言是静态类型语言。在代码编译的时候,所有类型都已经确定了。

interface{}——空接口。空接口是可以代表任意类型的。

Go语言的反射是建立在类型之上的。

Go语言中指定类型创建的变量叫做是静态类型,在创建变量的时候类型就已经确定。
反射主要与interface{}类型相关,它是具体类型,只有interface类型才有反射的说法。
Go语言中,变量包括(type、value)两部分,type又分为静态类型和具体类型。每个interface变量都有一个对应的pair,pair中记录了实际变量的(type、value)。interface{}包含两个指针,一个指针指向type,一个指针指向value。其中type是一个具体类型。
如果将一个interface{}类型,赋值给另一个interface{},则实际pair的信息是不会变的。也就是说值和类型都不会改变。
反射,本质上就是用来检测存储在interface{}中的pair的一种机制。pair中本质上就是存储的type和value。type和value也是反射包中最重要的两个类型。

反射reflect的使用

reflect的基本功能TypeOf和ValueOf
reflect反射包提供了两种方法,reflect.TypeOf()和reflect.ValueOf()

reflect.TypeOf()用来动态获取输入参数接口中的值的类型,如果接口为空返回nil。实际就是获取interface{}的pair中的type。
reflect.ValueOf()用来获取输入参数中的数据的值,如果为空返回0。实际就是获取interface{}的pair中的value。

所有的具体类型都可以看作是interface{}空接口的具体实现。

var x interface{}
var y string
 
func main() {
	x = 11.11
	fmt.Println("type x:", reflect.TypeOf(x))
	fmt.Println("value x:", reflect.ValueOf(x))
 
	y = "liqi-test"
	fmt.Println("type y:", reflect.TypeOf(y))
	fmt.Println("value y:", reflect.ValueOf(y))
}

reflect.TypeOf()返回的类型是Type,reflect.ValueOf()返回的类型是Vlue。对于Type和Vlue下,都包含了大量的方法。

反射获取结构体数据

type Person struct {
	Name string
	Age  int
	Sex  string
}
 
func (p Person) Say(msg string) {
	fmt.Println("hello,", msg)
}
 
func (p Person) PrintInfo() {
	fmt.Printf("姓名: %s, 年龄: %d, 性别: %s\n", p.Name, p.Age, p.Sex)
}
 
func main() {
	p1 := Person{"xiaoming", 18, "男"}
	getMsg(p1)
}
 
func getMsg(msg interface{}) {
	getType := reflect.TypeOf(msg)
	fmt.Println("Type name is:", getType.Name())
	fmt.Println("Kind is:", getType.Kind())
 
	getValue := reflect.ValueOf(msg)
	fmt.Println("get all Fields:", getValue)
 
	//获取结构体字段
	fmt.Println("---获取结构体字段信息---")
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		fieldValue := getValue.Field(i).Interface()
		fmt.Printf("结构体Field:%v, 值:%v, 值的类型: %T\n", field.Name, fieldValue, fieldValue)
	}
 
	//获取方法
	fmt.Println("---获取结构体方法信息---")
	for i := 0; i < getType.NumMethod(); i++ {
		getMethod := getType.Method(i)
		fmt.Printf("方法名称: %s, 方法类型: %s\n", getMethod.Name, getMethod.Type)
	}
}

定义了一个Person结构体,字段有Name、Age、Sex,还定义了结构体的两个方法。

reflect.TypeOf(xx).Name()        获取类型的名称
reflect.TypeOf(xx).Kind()        获取类型的种类
reflect.TypeOf(xx).NumField()        获取结构体字段的数量
reflect.ValueOf(xx).NumField()        获取结构体字段对应的值的数量
reflect.TypeOf(xx).Field(index)        按下标获取结构体字段
reflect.ValueOf(xx).Field(i).Interface()        按下标获取结构体内字段的值;如果不加.Interface()得到的类型是Vlue类型,interface()方法相当于将Vlue类型强制转换为字段本身类型。
reflect.TypeOf(xx).Method(index)         按下表获取结构体方法
reflect.TypeOf(xx).Method(index).Name()        方法的名称(函数名称)
reflect.TypeOf(xx).Method(index).Type()        方法的类型(函数类型) 
reflect.ValueOf(xx).Field(i).IsZero()       获取结构体字段对应的值是否为零或空

反射设置实际变量的值
需要修改实际变量的值,也是通过reflect.ValueOf(X)获取reflect.Value对象,然后进行变量值的修改。但需要注意的是reflect.ValueOf(X)中的X必须是一个指针对象,然后使用Elem().Set()进行更改,Set()传入的数值必须是Value类型。

func main() {
	var num float64 = 11.11
	fmt.Println("num的数值:", num)
 
	numValue := reflect.ValueOf(&num)
 
	fmt.Println("num的类型:", numValue.Elem().Type())
	fmt.Println("num是否可以修改:", numValue.Elem().CanSet())
 
	SetNum := reflect.ValueOf(22.22)
	numValue.Elem().Set(SetNum)
	fmt.Println("修改后num的数值:", num)
 
	fmt.Println("---修改结构体对象数值---")
	p1 := &Person{"xiaoming", 18, "男"}
	fmt.Println("修改前的结构体:", *p1)
	personValue := reflect.ValueOf(p1)
 
	fmt.Println("结构体对象是否可以修改?:", personValue.Elem().CanSet())
	personValue.Elem().FieldByName("Name").SetString("xiaohong")
	personValue.Elem().FieldByName("Age").SetInt(20)
	personValue.Elem().FieldByName("Sex").SetString("女")
	fmt.Println("修改后的结构体:", *p1)
}
 

反射调用方法

type Person struct {
	Name string
	Age  int
	Sex  string
}
 
func (p Person) Say(msg string) {
	fmt.Println("hello,", msg)
}
 
func (p Person) PrintInfo() {
	fmt.Printf("姓名: %s, 年龄: %d, 性别: %s\n", p.Name, p.Age, p.Sex)
}
 
func main() {
	p1 := Person{"xiaoming", 18, "男"}
	value := reflect.ValueOf(p1)
 
	method1 := value.MethodByName("Say")
	method2 := value.MethodByName("PrintInfo")
 
	//调用方法
	args1 := []reflect.Value{reflect.ValueOf("反射执行有参数的方法")}
	method1.Call(args1) //有参数
	method2.Call(nil)   //无参数
}

反射调用函数
函数的调用和方法类似。

func fun1() {
	fmt.Println("反射调用无参函数")
}
 
func fun2(msg string) {
	fmt.Println("hello,", msg)
}
 
func fun3(i int, s string) (a int, b string) {
	a = i + 1
	b = s + ",从函数中return"
	return a, b
}
 
func main() {
	//反射调用无参数函数
	funValue1 := reflect.ValueOf(fun1)
	if funValue1.Kind() == reflect.Func {
		funValue1.Call(nil)
	}
 
	//反射调用有参数函数
	funValue2 := reflect.ValueOf(fun2)
	if funValue2.Kind() == reflect.Func {
		funValue2.Call([]reflect.Value{reflect.ValueOf("反射调用有参函数")})
	}
 
	//反射调用有返回值函数
	funValue3 := reflect.ValueOf(fun3)
	if funValue3.Kind() == reflect.Func {
		resultValue := funValue3.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf("函数有返回值")})
		fmt.Println("有返回值函数的返回值:", resultValue[0].Interface(), resultValue[1].Interface())
	}
}

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!