持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情
前言
Go是一个提供很好的反射特性的静态语言,有很多框架都依赖于反射特性已简化代码,提升开发效率。
大部分的反射方法都在reflect包中,它提供了运行时反射能力,在包中有两个重要的类型reflect.type和reflect.value,分别对应类型的相关信息和类型的值。
- reflect.TypeOf方法来获得
reflect.type类型 - reflect.ValueOf方法来获得
reflect.value类型
反射类型之间转换关系大致如下图
reflect.Type类型
正如上面所说,通常都是直接使用reflect.TypeOf方法获得类型的信息的。
func testReflect() {
var i int = 1
var s string = "Hello"
var student Student
intType := reflect.TypeOf(i)
strType := reflect.TypeOf(s)
studentType := reflect.TypeOf(student)
fmt.Println(intType, strType, studentType)
// int string main.Student
}
获得结构体属性
通常都需要动态获得结构体的相关属性和相关标签信息,以便实现某些需求。
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func testStructReflect() {
studentType := reflect.TypeOf(Student{})
numField := studentType.NumField()
for i := 0; i < numField; i++ {
field := studentType.Field(i)
fmt.Printf("fieldName:%s\tfieldType:%v\ttagValue:%s\n", field.Name, field.Type, field.Tag.Get("json"))
}
}
// fieldName:Name fieldType:string tagValue:name
// fieldName:Age fieldType:int tagValue:age
除了通过简单的遍历,reflect包还提供了使用byName和ByIndex来获得字段信息
name, ok := studentType.FieldByName("Name")
fmt.Println(name, ok) // {Name string json:"name" 0 [0] false} true
age := studentType.FieldByIndex([]int{1})
fmt.Println(age) // {Age int json:"age" 16 [1] false}
获得结构体方法
获得结构体方法,注意这里是reflect.TypeOf(&Student{}),这是因为实体和指针方法集的问题啦,如果不明白为啥就需要去学习下啦。
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (s *Student) Say() {
fmt.Println(s.Name, s.Age)
}
func (s *Student) SetName(name string) {
s.Name = name
}
func testStructMethodReflect() {
studentType := reflect.TypeOf(&Student{})
numMethod := studentType.NumMethod()
for i := 0; i < numMethod; i++ {
method := studentType.Method(i)
methodType := method.Func.Type() // 获得方法本身的Type
fmt.Printf("methodName:%s\targsNum:%d\treturnNum:%d\n", method.Name, methodType.NumIn(), methodType.NumOut())
}
}
// methodName:Say argsNum:1 returnNum:0
// methodName:SetName argsNum:2 returnNum:0
指针类型转换值类型
有时因为上面提到的方法集的问题,有时需要将指针类型转换为值类型。
func testConvert() {
studentPointType := reflect.TypeOf(&Student{})
elem := studentPointType.Elem() // 获得所指向的值类型
fmt.Println(elem) // main.Student
}
判断是否实现某个接口
// 定义一个接口
type Sayer interface {
Say()
}
func testStructImplement() {
sayer := (*Sayer)(nil) // 获得接口指针类型
studentType := reflect.TypeOf(&Student{})
fmt.Println(studentType.Implements(reflect.TypeOf(sayer).Elem())) // true
}
reflect.Value类型
同样,如果我们要获得变量的值,就通过reflect.ValueOf方法来得到。
func testValueOf() {
var i int = 1
var str string = "Hello"
intValue := reflect.ValueOf(i)
strValue := reflect.ValueOf(str)
fmt.Println(intValue, strValue)
// 1 Hello
}
更新变量值
当我们想要更新变量值时,就通过reflect.Value.Set方法来设置。需要注意的是由于Go的函数调用都是值传递,因此经过interface{}的隐形转换,操作的对象跟一开始没有啥关系了,并不能修改到原始变量的值。
var i int = 1
intValue := reflect.ValueOf(i)
intValue.SetInt(2)
// reflect: reflect.Value.SetInt using unaddressable value
运行上面的代码,会导致程序崩溃,因此我们需要采用指针的方式来实现。
func testValueOf() {
var i int = 1
var str string = "Hello"
intValue := reflect.ValueOf(&i)
strValue := reflect.ValueOf(&str)
fmt.Println(intValue.Elem(), strValue.Elem()) // 1 Hello
intValue.Elem().SetInt(2)
strValue.Elem().SetString("World")
fmt.Println(intValue.Elem(), strValue.Elem())// 2 World
}
当调用相关Set方法时,方法内部会检测当前的字段是否可以被设置以及是否公开
func (v Value) Set(x Value) {
v.mustBeAssignable() // 检测是否可以设置
x.mustBeExported() // 检测是否公开
// ...
}
指针和值互相转换
func testValueConvert() {
student := Student{}
pointValue := reflect.ValueOf(&student)
fmt.Println(pointValue.Elem()) // 值类型
fmt.Println(pointValue.Elem().Addr()) // 指针类型
}
调用结构体方法
func testCall() {
studentValue := reflect.ValueOf(&Student{})
studentValue.Elem().FieldByName("Name").SetString("Tom")
studentValue.Elem().FieldByName("Age").SetInt(18)
method := studentValue.MethodByName("Say")
values := method.Call([]reflect.Value{}) // Tom 18
}
小小总结
本章介绍了reflect.Type和reflect.Value这两个重要的反射类型,以及平常开发中常用的相关API,Reflect包为我们提供了很多强大的功能,当过度使用反射也一样会降低程序性能。