文章目录
1. 反射是什么?
反射:程序可以访问、检测和修改它本身状态或行为的一种能力
Go语言的反射机制:在运行时更新变量和检查它们的值、调用它们的方法;但在编译时并不知道这些变量的具体类型
2. 反射有什么作用?
1)编写不定传参类型函数或传入类型过多
对象关系映射
type User struct {
gorm.Model
Name string
Age sql.NullInt64
Birthday *time.Time
Email string `gorm:"type:varchar(100);unique_index"`
Role string `gorm:"size:255"` // set field size to 255
MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
Num int `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
Address string `gorm:"index:addr"` // create index with name `addr` for address
IgnoreMe int `gorm:"-"` // ignore this field
}
var users []User
db.Find(&users)
2)不确定调用哪个函数,需要根据某些条件动态执行
参数
funcPtr
以接口的形式传入函数指针,参数args
以可变参数的形式传入 =>bridge
函数中可用反射来动态执行funcPtr
函数
func bridge(funcPtr interface{}, args ...interface{})
3. 如何实现反射?
Go的反射机制是通过接口来进行的
Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数 => 可以在运行时检测类型的信息、改变类型的值。
3.1 反射三定律
1)反射可将"接口类型变量"转换为"反射类型对象"
package main
import (
"fmt"
"reflect"
)
func main() {
num := 1
v := reflect.ValueOf(num)
t := reflect.TypeOf(num)
fmt.Println("Reflect : Num.Value = ", v) //Reflect : Num.Value = 1
fmt.Println("Reflect : Num.Type = ", t) //Reflect : Num.Type = int
}
解析:
reflect.ValueOf
/ reflect.TypeOf
的函数签名如下,两个方法的参数类型都是空接口。
func TypeOf(i interface{}) Type
func (v Value) Interface() (i interface{})
调用
reflect.TypeOf(x)
时:x
被存储在空接口中 =>reflect.TypeOf
对空接口进行拆解 => 将接口类型变量转换为反射类型变量
2)反射可将"反射类型对象"转换为"接口类型变量"
origin := v.Interface().(int)
根据一个
reflect.Value
类型的变量,可使用 Interface 方法恢复其接口类型的值
package main
import (
"fmt"
"reflect"
)
func main() {
num := 1
v := reflect.ValueOf(num)
t := reflect.TypeOf(num)
fmt.Println("Reflect : Num.Value = ", v) //Reflect : Num.Value = 1
fmt.Println("Reflect : Num.Type = ", t) //Reflect : Num.Type = int
origin := v.Interface().(int)
fmt.Println(origin)
}
3)若要修改"反射类型对象",其值必须"可写"
package main
import "reflect"
func main() {
num := 1
v := reflect.ValueOf(num)
v.SetInt(2)
}
上面的v.SetInt
报错
panic: reflect: reflect.Value.SetInt using unaddressable value
goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x82)
反射对象v包含的是副本值,所以无法修改!!
可通过CanSet
函数来判断反射对象是否可以修改
fmt.Println("v的可写性:", v.CanSet()) // false
3.2 反射小结
- 反射对象包含了接口变量存储的值 & 类型
- 可以通过反射对象修改原始值吗?
- 反射对象中包含的值是原始值 => 【可以】
- 反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址) => 【不可以】
4. 反射修改内容 & 调用方法
反射使代码执行效率较慢,因为
- 涉及内存分配 & 后续垃圾回收
reflect
实现里有大量枚举(for循环 e.g类型之类的)
p.Elem()
- 获取当前指针指向的元素值 (取元素,等效于对指针类型变量做*
操作)
package main
import (
"fmt"
"reflect"
)
func main() {
num := 1
fmt.Println(num) // 1
// 1) 通过反射修改内容
p := reflect.ValueOf(&num)
v := p.Elem()
v.SetInt(2)
fmt.Println(num) // 2
// 2) 通过反射调用方法
function := hello
functionV := reflect.ValueOf(function)
functionV.Call(nil)
}
func hello() {
fmt.Println("Hello world!")
}