您现在的位置是:首页 >技术杂谈 >Go | 一分钟掌握Go | 10 - 反射网站首页技术杂谈

Go | 一分钟掌握Go | 10 - 反射

Mars酱 2023-07-10 08:00:02
简介Go | 一分钟掌握Go | 10 - 反射

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

反射你以为只在Java中有吗?Go也有反射机制,很多的标准库中,也使用了反射机制,比如fmt包中的encoding包,都是依赖反射机制的。Go的反射比Java的更简单,就只依赖两两个对象:一个Type,一个Value,前者是得到定义对象的类型,比如定义int类型的变量a,反射之后得到int这个类型;后者是得到该变量的值,如果a没有赋值,那么反射得到的a的值就是0。

怎样反射得到对象的类型?

我们可以通过reflect的TypeOf()函数可以获得任意值的类型Type对象,我们试试:

import (
	"fmt"
	"reflect"
	"testing"
)

// author: mars酱
func Test_reflect2(t *testing.T) {
	var pi float32 = 3.1415926
	reflectType1(pi)
}

func reflectType1(x interface{}) {
    // 调用TypeOf函数
	typeName := reflect.TypeOf(x)
	fmt.Printf("类型为:%s
", typeName.Name())
}

我创建了一个函数reflectType1,函数内部调用TypeOf得到reflect.Type对象(看下面浮动窗的提示信息)

得到Type对象之后,再调用.Name()函数得到类型的名字,运行一下,得到结果:

我们得到了pi的类型是float32,是不是比java简单多了

怎样反射得到对象的值?

得到了类型对象,那么我们来反射获取一下对象的值。Go中通过反射获取值的方法是通过reflect的ValueOf()函数获取的。我们试试看:

import (
	"fmt"
	"reflect"
	"testing"
)

// author: mars酱
func Test_reflect2(t *testing.T) {
	var pi float32 = 3.1415926
	reflectType1(pi)

	reflectValue1(pi)

}

func reflectType1(x interface{}) {
	typeOf := reflect.TypeOf(x)
	fmt.Printf("类型为:%s
", typeOf)
}

func reflectValue1(x interface{}) {
	valueOf := reflect.ValueOf(x)
	fmt.Printf("值为:%v
", valueOf)
}

运行一下,得到结果:

相比java,是不是显得go真的太容易和简单很多啊

怎样反射得到对象结构体?

通常,我们并不是只有基本类型的对象的,我们庞大的业务系统是由很多自定义的结构体对象组成的,这里我们看怎么反射自定的结构体。先自定义一个结构体,然后赋值:

type person struct {
	name string
	age  int
}

man := person{age: 98, name: "mars酱"}

再定义一个函数,用来反射结构体:

func reflectStruct1(x interface{}) {
	// 1. 得到Type
	typeOf := reflect.TypeOf(x)
    // 2. 得到Value
	valueOf := reflect.ValueOf(x)

	// 3. 得到Type下的参数数量
	numField := typeOf.NumField()
	for i := 0; i < numField; i++ {
		// 得到StructField对象
		field := typeOf.Field(i)
		value := valueOf.Field(i)
		fmt.Printf("变量名:%s 索引下标:%d 类型:%v 值:%v 
", field.Name, field.Index, field.Type, value)
	}
}

运行之后,打印结果如下:

这样,就得到了结构体中属性的类型还有值,还得到了属性是下标顺序,name是第一个,下标索引为0,age是第二个,下标索引是1

反射调用函数吧

在java中,我们经常使用反射调用,知道一个对象的类型,去反射得到里面的函数,然后给函数传入参数之后,再调用反射出来的函数,这在java的框架中是家常便饭了,那么这次我们看看Go里面怎么做的。

先定义一个函数,一个简单的函数,我们让这个函数做个加法计算:

func addFunc1(a, b int) int {
	return a + b
}

然后创建一个反射这个函数的函数(稍微有点绕口):

// 反射addFunc1函数的函数
func reflectFunc(x interface{}, a, b int) {
	// 1. 先反射值对象
	funcValue := reflect.ValueOf(x)
	// 2. 传入两个参数给构造函数
	paramList := []reflect.Value{reflect.ValueOf(a), reflect.ValueOf(b)}
	// 3. 反射调用函数
	retList := funcValue.Call(paramList)
	// 4. 获取结果
	fmt.Printf("反射调用得到的结果:%v
", retList[0].Int())
}

这个反射函数需要传入三个参数:一个x对象,两个需要做加法的变量a和b。最后我们在单元测试函数中去调用:

// 调用反射函数的函数
reflectFunc(addFunc1, 10, 20)

运行一下,得到结果如下:

小结一下

Go的反射相比Java的更简单容易理解,但是有几个点需要知道:

  1. 反射数组、切片、Map、指针等类型的变量,它们调用.Name()函数,得到的都是空;
  2. 反射中的类型如果错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后
  3. 使用太多反射的代码会很难阅读和难以理解;
  4. 网传Go的反射的性能低下,而且基于反射实现的代码通常比正常代码运行速度慢一到两个数量级;

所以,斟酌而行。也希望未来Go的性能会更高,毕竟最新版才1.20.*版本呢~

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。