GO语言-反射reflect
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()) } }