反射 反射是许多强类型语言都会具有的功能,我们先看一下维基百科中对反射的定义
在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
反射带给我们了一种在高级语言中直接获取对象内存中的数据的途径,如果没有反射但我们却想获取对象的类型与内存信息,就只能使用汇编才能做到
为什么我们需要反射(反射的优点)
使用反射可以让我们在编写程序时,避免一部分的硬代码,使代码具有灵活性,变通性
使用反射给我们了在程序运行时修改源码结构(如方法,隐藏字段值,结构体定义等),可以给程序带来更大的可能性
反射有什么缺点?
因为反射的语法与概念都比较抽象,所以使用了反射的代码会使人难以阅读,不利于合作与交流
因为反射将部分信息检查工作从编译期推到了运行期,所以会影响代码执行的效率,滥用反射可能会很大程度降低程序的运行效率
面向反射的编程需要较多的高级知识,学习成本高。并且写出错误的反射代码的代价也很高,通常会直接引起程序的panic
Golang中的接口 因为Golang中的反射主要依赖于Golang中的接口类型,所以在我们正式开始学习反射之前,要先了解一下Golang中的接口。我想接触过Golang的程序员应该都知道Golang的Duck Typing,即“一个东西像鸭子一样有嘴巴,有眼睛,会游泳,会嘎嘎叫,那么我们就可以把这个东西看成鸭子”。在Golang中一个类型只要实现了某个接口中所有的方法,那么该类型就可以看作是实现了这个接口,使用隐式的接口声明。
在Golang的源码中,与接口相关的结构体主要有eface与iface这两个,这两个结构体的区别是,eface主要用来保存空接口对象,iface则用来保存实现了其他非空接口的对象。为什么空接口与非空接口需要两个不同的结构体来保存呢?这是与Golang的类型系统有关的,隐式的接口类型声明决定了某个对象的接口类型是在运行时才能被确定的,这种可能随程序运行而改变的类型,被称作动态类型。这样Golang中的空接口因为没有实现任何接口,所以只拥有静态类型数据,而实现了其他接口的对象,则同时拥有静态类型与动态类型。下面贴出了iface,eface以及其中涉及到的一些结构体的源码,可以看到iface与eface中都有data字段,该字段保存了一个指向接口对象数据的指针。_type用来存储静态类型,itab做了一层包装,同时存储动态类型与静态类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 type iface struct { tab *itab data unsafe.Pointer } type eface struct { _type *_type data unsafe.Pointer } type itab struct { inter *interfacetype _type *_type hash uint32 _ [4 ]byte fun [1 ]uintptr } type _type struct { size uintptr ptrdata uintptr hash uint32 tflag tflag align uint8 fieldalign uint8 kind uint8 alg *typeAlg gcdata *byte str nameOff ptrToThis typeOff }
反射的基本函数 在reflect包中定义了一个Type接口以及一个Value结构体,分别用来获取对象的类型信息,与获取修改对象的数据。同时也提供了两个基本函数分别来返回一个对象的Type接口与Value结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func TypeOf (i interface {}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) } func ValueOf (i interface {}) Value { if i == nil { return Value{} } escapes(i) return unpackEface(i) }
接下来我们来看一下Type接口中拥有哪些方法,以及Value结构体中的字段以及实现的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 type Type interface { Align() int FieldAlign() int Method(int ) Method MethodByName(string ) (Method, bool ) NumMethod() int Name() string PkgPath() string Size() uintptr String() string Kind() Kind Implements(u Type) bool AssignableTo(u Type) bool ConvertibleTo(u Type) bool Comparable() bool Bits() int ChanDir() ChanDir IsVariadic() bool Elem() Type Field(i int ) StructField FieldByIndex(index []int ) StructField FieldByName(name string ) (StructField, bool ) FieldByNameFunc(matchfunc(string )bool (StructField,bool )) In(i int ) Type Key() Type Len() int NumField() int NumIn() int NumOut() int Out(i int ) Type common() *rtype uncommon() *uncommonType } type Value struct { typ *rtype ptr unsafe.Pointer flag } func (v Value) SetLen (n int ) {}func (v Value) SetCap (n int ) {}func (v Value) SetMapIndex (key, val Value) {}func (v Value) Index (i int ) Value {}func (v Value) FieldByName (name string ) Value {}func (v Value) Int () int64 {}func (v Value) NumField () int {}func (v Value) TrySend (x reflect.Value) bool {}func (v Value) Call (in[]Value) (r []Value) {}func (v Value) CallSlice (in[]Value) []Value {}
反射代码用例 导出结构体中的未导出成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "reflect" "fmt" ) type People struct { Name string age int } func main () { liuyou := People{Name: "Liuyou" , age: 19 } v := reflect.ValueOf(&liuyou) f := v.Elem().FieldByName("Name" ) fmt.Println(f.String()) f.SetString("yqz" ) fmt.Println(f.String()) f = v.Elem().FieldByName("age" ) f.SetInt(20 ) fmt.Println(f.Int()) }
使用反射实现简易的validate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 func Validate (value interface {}) (ok bool , msg string ) { t := reflect.TypeOf(value) v := reflect.ValueOf(value) if t.Kind() != reflect.Struct { return false , "传入对象必须是一个结构体" } for i := 0 ; i < v.NumField(); i++ { tag := t.Field(i).Tag pattern := tag.Get("validate" ) if pattern == "" { return } patternList := strings.Split(pattern, "," ) for _, pattern = range patternList { if strings.HasPrefix(pattern, "required" ) { ok, msg = checkRequired(pattern, v.Field(i)) if !ok { return } } else if strings.HasPrefix(pattern, "number" ) { ok, msg = checkNumber(pattern, v.Field(i)) if !ok { return } } else if strings.HasPrefix(pattern, "max" ) { ok, msg = checkMax(pattern, v.Field(i)) if !ok { return } } else if strings.HasPrefix(pattern, "min" ) { ok, msg = checkMin(pattern, v.Field(i)) if !ok { return } } else if strings.HasPrefix(pattern, "email" ) { ok, msg = checkEmail(pattern, v.Field(i)) if !ok { return } } else if strings.HasPrefix(pattern, "phone" ) { ok, msg = checkPhone(pattern, v.Field(i)) if !ok { return } } } } return }
这里只给出checkRequired(检查字段是否为空)的规则处理函数,其余函数的实现原理大致一致,就是对每个字段对应的Value进行正则匹配,检查等,判断该字段的值是否符合对应的规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func checkRequired (pattern string , value reflect.Value) (ok bool , msg string ) { ruleList := strings.Split(pattern, ":" ) if value.IsValid() { ok = true return } if len (ruleList) >= 2 { msg = ruleList[1 ] } else { msg = "某个必需的字段为空" } ok = false return }
结束 好了,关于Golang反射的机制我们今天就先学习到这里了。每个事物都有自己的优点也有自己的缺点,反射可以通过获取对象的动态类型或者接口对象的值来让我们的代码更加具有兼容性与变通性,但程序中过多的反射代码,不仅会拖慢程序的运行,而且会使代码难以令外人阅读。所以在你的代码中适当(注意是适当哦)的使用反射,来使你的代码更加优雅!