反射的定义
- 反射是指一类的应用,他们能够 自描述 自控制
- python反射 根据字符串执行函数、根据字符串导入包
go中反射的简介
- go是静态语言。反射就是go提供一种机制,在编译时不知道类型的情况下,可以做如下的事情
- 更新变量
- 运行时查看值
- 调用方法
- 对他们的布局进行操作
为什么使用反射
两个经典场景
- 你编写的这个函数,还不知道传给你的类型具体是什么,可能是还没约定好,也可能是传入的类型很多
- 希望通过用户的输入来决定调用按个函数(根据字符串调用方法),动态执行函数
- 举例使用 interface.type判断类型
package main
import "fmt"
func main() {
var s interface{} = "abc"
switch s.(type) {
case string:
fmt.Println("s.type=string")
case int:
fmt.Println("s.type=int")
case bool:
fmt.Println("s.type=bool")
default:
fmt.Println("未知的类型")
}
}
- 上述类型判断的问题
- 类型判断会写很多,代码很长
- 类型还会增删,不灵活
使用反射获取变量内部的信息
- reflect包提供 valueOf和Typeof
- reflect.ValueOf : 获取输入接口中数据的值,如果为空返回 0
- reflect.Typeof : 获取输入接口中值的类型,如果为空返回 nil
- Typeof传入所有类型,因为所有的类型都实现了空接口
举例1 内置类型的测试
package main
import (
"log"
"reflect"
)
func main() {
var s interface{} = "abc"
// TypeOf会返回模板的对象
reflectType := reflect.TypeOf(s)
reflectValue := reflect.ValueOf(s)
log.Printf("[typeof:%v]", reflectType)
log.Printf("[valueof:%v]", reflectValue)
}
举例2 自定义struct的反射
- 生成的举例,对未知类型的进行 遍历探测它的Field,抽象成一个函数
- go语言里面struct成员变量小写,在反射的时候直接panic
reflect.Value.Interface: cannot return value obtained from unexported field or method
- 结构体方法名小写是不会panic,反射时也不会被查看到
- 指针方法是不能被反射查看到的
对于成员变量
- 先获取intereface的reflect.Type,然后遍历NumField
- 再通过reflect.Type的Field获取字段
- 最后通过Field的interface获取对应的value
对于方法
- 先获取intereface的reflect.Type,然后遍历NumMethod
- 再分别通过reflect.Type的 t.Method获取真实的方法
- 最后通过Name和Type获取方法的类型和值
package main
import (
"log"
"reflect"
)
type Person struct {
Name string
Age int
}
type Student struct {
Person //匿名结构体嵌套
StudentId int
SchoolName string
IsBaoSong bool //是考上来的吗
Hobbies []string
// panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
//hobbies []string
Labels map[string]string
}
func (s *Student) GoHome() {
log.Printf("[回家了][sid:%d]", s.StudentId)
}
func (s Student) GotoSchool() {
log.Printf("[去上学了][sid:%d]", s.StudentId)
}
func (s Student) baosong() {
log.Printf("[竞赛保送][sid:%d]", s.StudentId)
}
func main() {
s := Student{
Person: Person{Name: "xiaoyi", Age: 9900},
StudentId: 123,
SchoolName: "五道口皇家男子职业技术学院",
IsBaoSong: true,
Hobbies: []string{"唱", "跳", "Rap"},
//hobbies: []string{"唱", "跳", "Rap"},
Labels: map[string]string{"k1": "v1", "k2": "v2"},
}
// 获取目标对象
t := reflect.TypeOf(s)
log.Printf("[对象的类型名称:%s]", t.Name())
// 获取目标对象的值类型
v := reflect.ValueOf(s)
// 遍历获取成员变量
for i := 0; i < t.NumField(); i++ {
// Field 代表对象的字段名
key := t.Field(i)
value := v.Field(i).Interface()
//
if key.Anonymous {
log.Printf("[匿名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)
} else {
log.Printf("[命名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)
}
}
// 打印方法
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
log.Printf("[第:%d个方法][方法名称:%s][方法的类型:%v]", i+1, m.Name, m.Type)
}
/*
2021/07/10 15:21:07 [对象的类型名称:Student]
2021/07/10 15:21:07 [匿名字段][第:1个字段][字段名:Person][字段的类型:main.Person][字段的值:{xiaoyi 9900}]
2021/07/10 15:21:07 [命名字段][第:2个字段][字段名:StudentId][字段的类型:int][字段的值:123]
2021/07/10 15:21:07 [命名字段][第:3个字段][字段名:SchoolName][字段的类型:string][字段的值:五道口皇家男子职业技术学院]
2021/07/10 15:21:07 [命名字段][第:4个字段][字段名:IsBaoSong][字段的类型:bool][字段的值:true]
2021/07/10 15:21:07 [命名字段][第:5个字段][字段名:Hobbies][字段的类型:[]string][字段的值:[唱 跳 Rap]]
2021/07/10 15:21:07 [命名字段][第:6个字段][字段名:Labels][字段的类型:map[string]string][字段的值:map[k1:v1 k2:v2]]
2021/07/10 15:21:07 [第:1个方法][方法名称:GotoSchool][方法的类型:func(main.Student)]
*/
}
- 抽成一个函数
package main
import (
"log"
"reflect"
)
type Person struct {
Name string
Age int
}
type Student struct {
Person //匿名结构体嵌套
StudentId int
SchoolName string
IsBaoSong bool //是考上来的吗
Hobbies []string
// panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
//hobbies []string
Labels map[string]string
}
//func (s *Student) GoHome() {
// log.Printf("[回家了][sid:%d]", s.StudentId)
//}
func (s Student) GoHome() {
log.Printf("[回家了][sid:%d]", s.StudentId)
}
func (s Student) GotoSchool() {
log.Printf("[去上学了][sid:%d]", s.StudentId)
}
func (s Student) Baosong() {
log.Printf("[竞赛保送][sid:%d]", s.StudentId)
}
func main() {
s := Student{
Person: Person{Name: "xiaoyi", Age: 9900},
StudentId: 123,
SchoolName: "五道口皇家男子职业技术学院",
IsBaoSong: true,
Hobbies: []string{"唱", "跳", "Rap"},
//hobbies: []string{"唱", "跳", "Rap"},
Labels: map[string]string{"k1": "v1", "k2": "v2"},
}
p := Person{
Name: "李逵",
Age: 124,
}
reflectProbeStruct(s)
reflectProbeStruct(p)
}
func reflectProbeStruct(s interface{}) {
// 获取目标对象
t := reflect.TypeOf(s)
log.Printf("[对象的类型名称:%s]", t.Name())
// 获取目标对象的值类型
v := reflect.ValueOf(s)
// 遍历获取成员变量
for i := 0; i < t.NumField(); i++ {
// Field 代表对象的字段名
key := t.Field(i)
value := v.Field(i).Interface()
//
if key.Anonymous {
log.Printf("[匿名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)
} else {
log.Printf("[命名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)
}
}
// 打印方法
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
log.Printf("[第:%d个方法][方法名称:%s][方法的类型:%v]", i+1, m.Name, m.Type)
}
/*
2021/07/10 15:21:07 [对象的类型名称:Student]
2021/07/10 15:21:07 [匿名字段][第:1个字段][字段名:Person][字段的类型:main.Person][字段的值:{xiaoyi 9900}]
2021/07/10 15:21:07 [命名字段][第:2个字段][字段名:StudentId][字段的类型:int][字段的值:123]
2021/07/10 15:21:07 [命名字段][第:3个字段][字段名:SchoolName][字段的类型:string][字段的值:五道口皇家男子职业技术学院]
2021/07/10 15:21:07 [命名字段][第:4个字段][字段名:IsBaoSong][字段的类型:bool][字段的值:true]
2021/07/10 15:21:07 [命名字段][第:5个字段][字段名:Hobbies][字段的类型:[]string][字段的值:[唱 跳 Rap]]
2021/07/10 15:21:07 [命名字段][第:6个字段][字段名:Labels][字段的类型:map[string]string][字段的值:map[k1:v1 k2:v2]]
2021/07/10 15:21:07 [第:1个方法][方法名称:GotoSchool][方法的类型:func(main.Student)]
*/
}
举例3 反射修改值
- 必须是指针类型
- pointer.Elem().Setxxx()
package main
import (
"log"
"reflect"
)
func main() {
var num float64 = 3.14
log.Printf("[num原始值:%f]",num)
// 通过reflect.ValueOf获取num中的value
// 必须是指针才可以修改值
pointer:=reflect.ValueOf(&num)
newValue:=pointer.Elem()
// 赋值
newValue.SetFloat(5.6)
log.Printf("[num新值:%f]",num)
pointer = reflect.ValueOf(num)
// reflect: call of reflect.Value.Elem on float64 Value
newValue = pointer.Elem()
}
举例4 反射调用方法
- 过程说明
- 首先reflect.ValueOf(p1)获取 得到反射类型对象
- reflect.ValueOf.MethodByName ,需要传入准确的方法名称,MethodByName代表注册
- 名字错了 会panic: reflect: call of reflect.Value.Call on zero Value
- []reflect.Value ,这是最终需要调用方法的参数,无参数传空切片
package main
import (
"log"
"reflect"
)
type Person struct {
Name string
Age int
Gender string
}
func (p Person) ReflectCallFuncWithArgs(name string, age int) {
log.Printf("[调用的是带参数的方法][args.name:%s][args.age:%d][[p.name:%s][p.age:%d]",
name,
age,
p.Name,
p.Age,
)
}
func (p Person) ReflectCallFuncWithNoArgs() {
log.Printf("[调用的是不带参数的方法]")
}
func main() {
p1 := Person{
Name: "小乙",
Age: 18,
Gender: "男",
}
// 1. 首先通过 reflect.ValueOf(p1)获取 得到反射值类型
getValue := reflect.ValueOf(p1)
// 2. 带参数的方法调用
methodValue := getValue.MethodByName("ReflectCallFuncWithArgs")
// 参数是reflect.Value的切片
args := []reflect.Value{reflect.ValueOf("李逵"), reflect.ValueOf(30)}
methodValue.Call(args)
// 3. 不带参数的方法调用
methodValue = getValue.MethodByName("ReflectCallFuncWithNoArgs")
// 参数是reflect.Value的切片
args = make([]reflect.Value, 0)
methodValue.Call(args)
}
结构体标签和反射
- json的标签解析json
- yaml的标签解析yaml
- xorm gorm的标签 标识db字段
- 自定义标签
- 原理是t.Field.Tag.Lookup(“标签名”)
- 混合的例子如下
package main
import (
"encoding/json"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
"reflect"
)
type Person struct {
Name string `json:"name" yaml:"yaml_name" mage:"name"`
Age int `json:"age" yaml:"yaml_age" mage:"age"`
City string `json:"-" yaml:"yaml_city" mage:"-"`
}
//json解析
func jsonWork() {
// 对象marshal成字符串
p := Person{
Name: "xiaoyi",
Age: 18,
City: "北京",
}
data, err := json.Marshal(p)
if err != nil {
log.Printf("[json.marshal.err][err:%v]", err)
return
}
log.Printf("[person.marshal.res][res:%v]", string(data))
// 从字符串解析成结构体
p2Str := `
{
"name":"李逵",
"age":28,
"city":"山东"
}
`
var p2 Person
err = json.Unmarshal([]byte(p2Str), &p2)
if err != nil {
log.Printf("[json.unmarshal.err][err:%v]", err)
return
}
log.Printf("[person.unmarshal.res][res:%v]", p2)
}
// yaml读取文件
func yamlWork() {
filename := "a.yaml"
content, err := ioutil.ReadFile(filename)
if err != nil {
log.Printf("[ioutil.ReadFile.err][err:%v]", err)
return
}
p := &Person{}
//err = yaml.Unmarshal(content, p)
err = yaml.UnmarshalStrict(content, p)
if err != nil {
log.Printf("[yaml.UnmarshalStrict.err][err:%v]", err)
return
}
log.Printf("[yaml.UnmarshalStrict.res][res:%v]", p)
}
func jiexizidingyibiaoqian(s interface{}) {
// typeOf type类型
r := reflect.TypeOf(s)
value := reflect.ValueOf(s)
for i := 0; i < r.NumField(); i++ {
field := r.Field(i)
key := field.Name
if tag, ok := field.Tag.Lookup("mage"); ok {
if tag == "-" {
continue
}
log.Printf("[找到了mage标签][key:%v][value:%v][标签:mage=%s]",
key,
value.Field(i),
tag,
)
}
}
}
func main() {
//jsonWork()
//yamlWork()
p := Person{
Name: "xiaoyi",
Age: 18,
City: "北京",
}
jiexizidingyibiaoqian(p)
}
反射的副作用
1.代码可读性变差
2.隐藏的错误躲过编译检查
- go静态语言,编译器能发现类型的错误
- 但是对于反射代码是无能为力的,可能运行很久才会panic
- 反射调用方法的副作用,将string参数传成 int
panic: reflect: Call using float64 as type int
goroutine 1 [running]:
reflect.Value.call(0x31c260, 0xc0000783c0, 0x293, 0x328479, 0x4, 0xc00011df48, 0
x2, 0x2, 0x353a00, 0xc0000200c0, ...)
C:/Program Files/Go/src/reflect/value.go:406 +0x1337
reflect.Value.Call(0x31c260, 0xc0000783c0, 0x293, 0xc00011df48, 0x2, 0x2, 0xc000
0783c0, 0x293, 0xc000047f30)
C:/Program Files/Go/src/reflect/value.go:337 +0xc5
main.main()
D:/nyy_work/go_path/src/maday06/reader.go:41 +0x316
3. go反射性能问题
type := reflect.value(obj)
fieldValue:=type_.FieldByName("xx")
- 每次取出的fieldValue类型是reflect.value
- 它是一个具体的值,不是一个可复用的对象
- 每次反射都要malloc这个reflect.Value结构退,还有GC
- 反射比正常的代码要慢1-2个数据量级,如果是追求性能的关键模块应减少反射