对于反射,之前的文章已经有所介绍,传送门:《运行时反射,深度解析!》,此处我们讲下反射三定律。
反射
Go语言提供了一种机制,在运行时可以更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。
官方对此有个非常简明的介绍,两句话耐人寻味:
反射提供一种让程序检查自身结构的能力
反射是困惑的源泉
静态类型
我们知道,Go是静态类型语言,例如”int”、”float32”、”[]byte”等等。每个变量在编译时就确定了自身的静态类型。
特殊的静态类型 interface
interface 类型是一种特殊的类型,它代表方法集合,可以用来存放任何实现了其方法的值。
最特殊的 interface 类型为空 interface 类型,即 interface {}
,interface用来表示一组方法集 合,所有实现该方法集合的类型都被认为是实现了该接口。所以空 interface 类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口。 所以一个空interface类型变量可以存放所有值,这也是有些人认为Go是动态类型的原因,这是个错觉。
反射三定律
interface 类型有个(value,type)对,Go 提供了用来提取 interface 的 value 和 type 的方法,反射就是检查 interface 的这个(value, type)对的。
reflect.Type
提供一组接口处理 interface 的类型,即(value, type)中的 type。
reflect.Value
提供一组接口处理 interface 的值,即(value, type)中的 value。
反射第一定律:反射可以将 interface 类型变量转换成反射对象
通过反射获取一个变量的值和类型,示例:
packagemainimport("fmt""reflect")funcmain(){varxfloat64=3.4t:=reflect.TypeOf(x)fmt.Println("type:",t)v:=reflect.ValueOf(x)fmt.Println("value",v)}
运行结果:
type:float64value3.4
反射是针对 interface 类型的变量,TypeOf()
和 ValueOf()
接受的参数都是 interface{} 类型的,即 x 值是被转成了 interface 传入的。
反射第二定律:反射可以将反射对象还原成 interface 对象
packagemainimport("fmt""reflect")funcmain(){varxfloat64=3.4v:=reflect.ValueOf(x)//visreflext.Valuevaryfloat64=v.Interface().(float64)fmt.Println("value",y)}
运行结果:
value3.4
对象 x 转换成反射对象 v,v 又通过 Interface() 接口转换成了 interface 对象,interface 对象通过.(float64)类型断言获取 float64 类型的值。
断言格式为:s = x.(T),意思是如果 x 所持有的元素如果同样实现了 T 接口,那么就把值传递给 s。
反射第三定律:反射对象可修改,value值必须是可设置的
通过反射可以将 interface 类型变量转换成反射对象,可以使用该反射对象设置其持有的值。
错误示例:
packagemainimport("reflect")funcmain(){varxfloat64=3.4v:=reflect.ValueOf(x)//visreflext.Valuev.SetFloat(6.6)//Error}
上面程序会发生 panic ,原因即是 v 是不可修改的。 传入 reflect.ValueOf() 函数的其实是 x 的值,而非 x 本身。即通过 v 修改其值是无法影响 x 的,所以会报错。 如果构建 v 时使用 x 的地址就可实现修改了,但此时 v 代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是 *v
。 那怎么通过 v 修改 x 的值呢?
reflect.Value 提供了 Elem() 方法,可以获得指针指向的 value 。
示例:
packagemainimport("fmt""reflect")funcmain(){varxfloat64=3.4v:=reflect.ValueOf(&x)v.Elem().SetFloat(6.6)fmt.Println("x:",v.Elem().Interface())}
运行结果:
x:6.6