持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情
Go 语言使用其自带的 reflect 包来实现反射。其反射机制就是程序在运行过程中,可以动态地调用对象的方法和属性。
我们知道,一个变量由类型和值两部分组成,类型又包括静态类型和具体类型。静态类型就是在编码时可见的类型(int、string等),而具体类型是在程序运行时系统所见的类型。
interface
interface(接口) 类型是一种特殊的类型,interface 用来表示一组方法集合,所有实现该方法集合的类型都可以被认为是实现了该接口。所以空 interface 类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口。
反射有两种类型 reflect.Value 和 reflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOf 和 reflect.TypeOf 分别获取任意对象的 reflect.Value 和 reflect.Type。
使用示例:
package main
import (
"fmt"
"reflect"
)
func main(){
var x float64 = 3.4
t := reflect.TypeOf(x)
fmt.Println("type:", t)
v := reflect.ValueOf(x)
fmt.Println("value", v)
}
//运行结果
//type: float64
//value 3.4
reflect
- reflect.Value
reflect.ValueOf() 定义如下:
func ValueOf(i interface{}) Value {...}
ValueOf() 函数用来获取输入参数接口中的数据的值。如果接口为空,则返回 0。
Value 类型为:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
示例,修改 struct 结构体字段的值:
func main() {
p := person{Name: "微客鸟窝",Age: 18}
pv:=reflect.ValueOf(&p)
pv.Elem().Field(0).SetString("无尘")
fmt.Println(p)
}
type person struct {
Name string
Age int
}
- reflect.Type
reflect.TypeOf() 定义如下:
func TypeOf(i interface{}) Type {...}
TypeOf() 函数用来动态获取输入参数接口中的值的类型,如果接口为空,则返回 nil。
reflect.Type 实际上是一个接口,定义了很多方法来获取类型相关的信息:
type Type interface {
Implements(u Type) bool //方法用于判断是否实现了接口 u;
AssignableTo(u Type) bool //方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;
ConvertibleTo(u Type) bool //方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;
Comparable() bool //方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。
//以下这些方法和Value结构体的功能相同
Kind() Kind
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
}
示例,遍历结构体的字段和方法:
package main
import (
"fmt"
"reflect"
)
func main() {
p := person{Name: "微客鸟窝", Age: 18}
pt := reflect.TypeOf(p)
//遍历person的字段
for i := 0; i < pt.NumField(); i++ {
fmt.Println("字段:", pt.Field(i).Name)
}
//遍历person的方法
for i := 0; i < pt.NumMethod(); i++ {
fmt.Println("方法:", pt.Method(i).Name)
}
}
type person struct {
Name string
Age int
}
func (p person) String() string{
return fmt.Sprintf("Name is %s,Age is %d",p.Name,p.Age)
}
反射3大法则
- 反射可以将 interface 类型变量转换成反射对象 通过 reflect 包的一些函数,可以把接口转换为反射定义的对象。
- reflect.ValueOf() 获取某个变量的值
- reflect.TypeOf() 获取某个变量的静态类型
- reflect.Value.Kind() 获取变量值的底层类型,底层类型可能为int/float/struct/slice等
- reflect.Value.Type() 获取变量值的类型,等同于reflect.TypeOf()
- 反射可以将反射对象还原成 interface 对象
package main
import (
"fmt"
"reflect"
)
func main(){
var x float64 = 3.4
v := reflect.ValueOf(x) //v is reflext.Value
var y float64 = v.Interface().(float64)
fmt.Println("value", y)
}
//运行结果:
//value 3.4
对象 x 转换成反射对象 v,v 又通过 Interface() 接口转换成了 interface 对象,interface 对象通过.(float64)类型断言获取 float64 类型的值。 断言格式为:s = x.(T),意思是如果 x 所持有的元素如果同样实现了 T 接口,那么就把值传递给 s。
- 反射对象可修改,value值必须是可设置的
当使用 TypeOf() 和 ValueOf() 时,如果传递的不是接口变量的指针,那么反射里的变量值是一个副本值,对反射对象进行修改时,并不能修改真实的值。
错误示例:
package main
import (
"reflect"
)
func main(){
var x float64 = 3.4
v := reflect.ValueOf(x) //v is reflext.Value
v.SetFloat(6.6) //Error
}
上面的程序会发生 panic ,因为 v 是不可修改的。
- 传入
reflect.ValueOf()函数的其实是 x 的副本值,而并非 x 本身。所以通过 v 修改其值是无法影响 x 的,所以会报错。 - 若要修改,我们可以在 ValueOf()中传入 x 的地址,此时 v 代表的是指针地址,如何通过指针地址 v 修改 x 的值呢?
- 使用 reflect.Value 的 Elem() 方法,可以获得指针指向的 value 。
示例:
package main
import (
"fmt"
"reflect"
)
func main(){
var x float64 = 3.4
v := reflect.ValueOf(&x)
v.Elem().SetFloat(6.6)
fmt.Println("x :", v.Elem().Interface())
}
//运行结果
//x : 6.6