写点什么

Go 语言入门很简单:Go 反射

作者:宇宙之一粟
  • 2022 年 2 月 26 日
  • 本文字数:1874 字

    阅读完需:约 6 分钟

Go 语言入门很简单:Go 反射

Go 语言也提供了 Java 类似的功能:反射。在编译不知道类型的情况下做到动态地操纵对象的值:

  • 更新变量

  • 运行时查看值

  • 调用方法

什么是反射

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


反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。

反射可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。


reflect 包

Go 语言中的反射是由 reflect 包提供支持的,reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。


reflect 包实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,两个函数分别是:


func reflect.ValueOf(interface interface{}) Value
复制代码

获取类型

Go 使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type)

package main
import ( "fmt" "reflect")
func main() { hello := "Hello,World!"
typeOfHello := reflect.TypeOf(hello)
fmt.Println(typeOfHello.Name(), typeOfHello.Kind())}
复制代码


运行该代码:

λ go run reflect_demo.gostring string
复制代码

代码解释:

  • 第 5 行导入 reflect

  • 第 10 行定义了一个 “Hello,World!”的字符串变量 hello

  • 第 12 行定义一个 typeOfHello 的变量接收 hello 的类型

  • 第 14 行打印出 typeOfHello 变量的类型名为 string,类别(Kind)为 string。


(Type)和类别(Kind)的区别:Type 表示 Go 语言的一个类型,它是一个有很多方法的接口,可以用来识别一个结构的字段或者一个函数的各个参数。但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如需要统一判断类型中的指针时,使用类别(Kind)信息就较为方便。


reflect.Type 和 reflect.Value

reflect.TypeOf 函数接受任何的 interface{} 参数,并且把接口中的动态类型以 reflect.Type 形式返回,它返回的总是具体类型(而不会是接口类型)。


reflect.Value 可以包含一个任意类型的值。可以返回一个接口值。


reflect.ValueOf 函数接收任意的 interface{} 并将接口的动态值以 reflect.Value 的形式返回。同样也是返回具体值。


package main
import ( "fmt" "reflect")
func main() { x := []int{21, 22} var y reflect.Value = reflect.ValueOf(&x) y = y.Elem() fmt.Println("The Slice is:", x) y = reflect.Append(y, reflect.ValueOf(2021)) fmt.Println("追加元素的 Slice为:", y)}
复制代码

运行代码:

λ go run main.goThe Slice is: [21 22]追加元素的 Slice为: [21 22 2021]
复制代码

通过反射修改值

反射也可以用来修改变量的值,那么一定要传入变量的指针类型,这样才能修改原来的值。同时需要使用到reflect.ValueOf(v).Elem() 方法。

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


package main
import ( "fmt" "reflect")

// 使用interface{}接收任意类型的反射func transType(v interface{}) {
rVal := reflect.ValueOf(v) // 将interface{}转成reflect.Value类型 rVal.Elem().SetInt(2023)
}
func main() { var num = 1999
transType(&num) fmt.Println(num)}
复制代码

运行代码:

λ go run main.go2023
复制代码

在数组上使用

也可以在数组的整数上使用反射 Append 和 ValueOF 函数:


package main
import ( "fmt" "reflect")
func main() { origin := reflect.ValueOf([]int{11, 12, 19}) result := reflect.ValueOf([]int{13, 21, 31}) fmt.Println("The value after using reflect is", origin, result)}
复制代码

运行代码,结果为:

λ go run main.goThe value after using reflect is [11 12 19] [13 21 31]
复制代码


总结

在 Golang 反射中,反射允许我们在动态处更改或修改对象或任何变量。Go 作为静态类型语言,需要利用 Go 语言的 reflect 包反映来使用此功能。 可以通过 reflect.Type 获得任意变量类型,通过 reflect.value 获取或者设置实际变量的值。


参考资料:

发布于: 刚刚阅读数: 2
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020.05.07 加入

🏆InfoQ写作平台-第二季签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于软件工程师,弹性不加班 热衷分享,执着于阅读写作,佛系不水文

评论

发布
暂无评论
Go 语言入门很简单:Go 反射