Go语言学习教程之反射的示例详解
目录
- 介绍
- 反射的规律
- 1. 从接口值到反射对象的反射
- 2. 从反射对象到接口值的反射
- 3. 要修改反射对象,该值一定是可设置的
介绍
reflect包实现运行时反射,允许一个程序操作任何类型的对象。典型的使用是:取静态类型interface{}
的值,通过调用TypeOf
获取它的动态类型信息,调用ValueOf
会返回一个表示运行时数据的一个值。本文通过记录对reflect包的简单使用,来对反射有一定的了解。本文使用的Go版本:
$ go version go version go1.18 darwin/amd64
在了解反射之前,先了解以下几个概念:
- 静态类型:每个变量都有一个静态类型,这个类型是在编译时(compile time)就已知且固定的。
- 动态类型:接口类型的变量还有一个动态类型,是在运行时(run time)分配给变量的值的一个非接口类型。(除非分配给变量的值是
nil
,因为nil
没有类型)。 - 空接口类型
interface{}
(别名any
)表示空的方法集,它可以是任何值的类型,因为任何值都满足有0或多个方法(有0个方法一定是任何值的子集)。 - 一个接口类型的变量存储一对内容:分配给变量的具体的值,以及该值的类型描述符。可以示意性地表示为
(value, type)
对,这里的type
是具体的类型,而不是接口类型。
反射的规律
1. 从接口值到反射对象的反射
在基本层面,反射只是检测存储在接口变量中的(value, type)
对的一种机制。
可以使用reflect包的 reflect.ValueOf
和reflect.TypeOf
方法,获取接口变量值中的(value, type)
对,类型分别为reflect.Value
和reflect.Type
。
(1)TypeOf方法:
func TypeOf(i any) Type
TypeOf
返回表示i
的动态类型的反射Type
。如果i
是nil
,那么返回nil
。
(2)ValueOf方法:
func ValueOf(i any) Value
ValueOf
返回一个新的Value
,初始化为存储在接口i
中的具体值。ValueOf(nil)
会返回零Value
。这个零Value
是反射对象中表示没有值的Value
。
var a interface{} = 1 var b interface{} = 1.11 var c string = "aaa" // 将接口类型的变量运行时存储的具体的值和类型显示地获取到 fmt.Println("type:", reflect.TypeOf(a)) // type: int fmt.Println("value:", reflect.ValueOf(a)) // value: 1 fmt.Println("type:", reflect.TypeOf(nil)) // type: <nil> fmt.Println("value:", reflect.ValueOf(nil)) // value: <invalid reflect.Value> fmt.Println("type:", reflect.TypeOf(b)) // type: float64 fmt.Println("value:", reflect.ValueOf(b)) // value: 1.11 fmt.Println("type:", reflect.TypeOf(c)) // type: string fmt.Println("value:", reflect.ValueOf(c)) // value: aaa
reflect.Value
的 Type
方法,返回一个 reflect.Value
的类型。
reflect.Value
的String
方法,将reflect.Value
的底层值作为字符串返回。
fmt.Printf
使用了反射:
%T
使用reflect.TypeOf
,拿到变量的动态类型。%v
深入到reflect.Value
内部拿到变量具体的值。
var a interface{} = 1 fmt.Println("type:", reflect.ValueOf(a).Type()) // type: int fmt.Println("string:", reflect.ValueOf(a).String()) // string: <int Value> fmt.Printf("type: %T \n", a) // type: int fmt.Printf("string: %v \n", a) // string: 1
Type
和 Value
都有一个 Kind
方法,返回一个表示存储的项的类型的常量。 比如Uint
, Float64
, Slice
等。
Value
的类似Int
和Float
这种名称的方法能够获取存储在内部的值。
Value
的“getter”(取值) 和 “setter”(设置值)会对能保存该值的最大类型进行操作。比如对于所有有符号整数,都是int64。
var a interface{} = 1 var b interface{} = 1.11 reflectA := reflect.ValueOf(a) fmt.Println("kind: ", reflectA.Kind()) // kind: int reflectIntA := reflectA.Int() // 返回的是 能存储有符号整数的最大类型 的值 reflectFloatB := reflect.ValueOf(b).Float() // 返回的是 能存储浮点数的最大类型 的值 var a1 int64 = reflectIntA var b1 float64 = reflectFloatB // var a2 int32 = reflectIntA // 会报错:cannot use reflectIntA (variable of type int64) as int32 value in variable // var b2 float32 = reflectFloatB // 会报错:cannot use reflectFloatB (variable of type float64) as float32 value in variable fmt.Println("a1: ", a1, "b1: ", b1) // a1: 1 b1: 1.11
2. 从反射对象到接口值的反射
像物理反射一样,Go 中的反射产生了它自己的逆。可以使用reflect.Value
的Interface
方法还原一个接口值。Interface()
将类型和值信息打包回一个接口表示。
Interface
方法:
func (v Value) Interface() (i any)
Interface
将v
的当前值作为一个interface{}
返回。它等同于:
var i interface{} = (v 的底层值)
练习代码:
var a interface{} = 1 reflectA := reflect.ValueOf(a) var a3 interface{} = reflectA.Interface() var a4 int = reflectA.Interface().(int) // 使用接口值的类型断言 fmt.Println(a3, a4) // 1 1 ,因为fmt.Println接收接口类型interface{}的参数,使用 reflect.Value 拿到具体的值,所以打印出运行时的具体结果
3. 要修改反射对象,该值一定是可设置的
可设置性是reflect.Value
的一个属性,表示一个反射对象可以修改用于创建该反射对象的实际存储。可设置性可以通过CanSet
方法获得。如果对不可设置的reflect.Value
调用Set
方法,就会报错。
使用reflect.Value
类型的Elem
方法能通过指针间接寻址得到一个可设置性为真的reflect.Value
。
var d float64 = 2.222 fmt.Println(reflect.ValueOf(d).CanSet()) // false reflectD := reflect.ValueOf(&d).Elem() fmt.Println(reflectD.CanSet()) // true reflectD.SetFloat(3.33) fmt.Println(d, reflectD) // 3.33 3.33
reflect.ValueOf(d)
是通过复制d
中的内容得到的reflect.Value
类型的值,它复制的内容存放的内存地址 和d
的值存放的内存地址是不同的。所以不能通过它来修改d
中原本存储的内容。
上面的代码中可以看到,调用SetFloat(3.33)
之后,反射对象reflectD
和创建该反射对象的d
都发生了改变。
使用反射修改结构体的字段:
type T struct { A int B string } t := T{111, "xxx"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { // NumField 返回结构体中的字段数量 f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, // 获取第i个字段的名称 f.Type(), // 获取第i个字段的类型 f.Interface(), // 将第i个字段转换回接口类型的值 ) // 0: A int = 111 // 1: B string = xxx } s.Field(0).SetInt(222) // 设置结构体的第一个字段的值 s.Field(1).SetString("yyy") // 设置结构体的第二个字段的值 fmt.Println(t) // {222 yyy}
上述练习代码都在一个reflect.go文件中,练习时在终端执行go run reflect.go
运行该文件。
以上就是Go语言学习教程之反射的示例详解的详细内容,更多关于Go语言 反射的资料请关注我们其它相关文章!