文章目录
- reflect包
- TypeOf
- type name和type kind
- ValueOf
- 通过反射获取值
- 通过反射设置变量的值
- 函数反射
- 结构体反射
- 与结构体相关的方法
- StructField类型
- 结构体反射示例
- 反射在GORM中的运用
- 为什么我们吐槽反射太慢
- 小结
? 反射这个概念比较常见了,在其他语言中也有反射这个概念(不过还有的语言不支持),
-
不知道函数的参数是什么,或者入参类型很多,没有办法统一表示,那就用反射;
-
根据某些条件决定调用不同的函数,那就根据函数和函数的参数进行反射。
? 但是在实际的工程代码里,比较少看到运用反射去做什么事情,首先是反射相关的代码不太好阅读以及比代码运行速度要慢一些还有使用不当会引发
-
实现go语言中的数据类型与文本/二进制数据进行转换,其实就是通过反射完成序列化和反序列化。
1
2func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error -
很多时候我们在部署服务的时候需要去读取一个
yaml 类型的配置文件,那么通过反射我们可以去解析获得的配置文件。 -
针对go语言,有一些不错的orm框架,使用它们的时候我们需要自己去定义结构体,orm框架做的事情就是把model和要操作的数据库表对应起来,如何实现映射呢,其实也用到了反射。
-
在go中判断两个
map 的key-value是否完全相同的时候,需要用到一个函数DeepEqual 。在之前的java 中要去判断key是否相同呢,我们需要利用key对应的hash值以及通过== 判断key的内容是否相同,但是go 就没有java 这么花里胡哨, 那么在DeepEqual 是怎么做的呢,就用到了反射,所以可以看到这个函数实在reflect包下面的。下面源码中的ValueOf()其实就是用到反射的体现。1
2
3
4
5
6
7
8
9
10
11func DeepEqual(x, y interface{}) bool {
if x == nil || y == nil {
return x == y
}
v1 := ValueOf(x)
v2 := ValueOf(y)
if v1.Type() != v2.Type() {
return false
}
return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}go 有一个地方需要注意下,它有底层类型和静态类型之说(后面提到kind和type的时候举了个例子),结合deepequal函数举个例子说明一下。两个变量的静态类型一个是MyInt,一个是YourInt,虽然底层类型都是int,值也都是1,但我们并不认为这样的两个变量是相等的。判断相等具体标准,可以点我。1
2
3
4
5
6
7
8
9type MyInt int
type YourInt int
func main() {
m := MyInt(1)
y := YourInt(1)
fmt.Println(reflect.DeepEqual(m, y)) // false
}
? 到此为止,大概对之前语言中的反射和Go中的反射能干啥做了一个简单的总结。接下来看看
reflect包
?
? 下面两个函数我们可以分别用来获取接口和结构体。
1 2 | func TypeOf(i interface{}) Type func ValueOf(i interface{}) Value |
TypeOf
? 先说
1 2 3 4 5 | func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) //eface.type指的是动态类型 return toType(eface.typ) } |
? 可以看到在将
? 书接上文,返回结果的时候通过
1 2 3 4 5 6 | func toType(t *rtype) Type { if t == nil { return nil } return t } |
? 在Go语言圣经中讲反射的部分提到了
? 下面对于使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | type cat int func reflectType(x interface{}) { v := reflect.TypeOf(x) fmt.Printf("type:%v\n", v) } func main() { var a float32 = 3.14 reflectType(a) // type:float32 var b int64 = 100 reflectType(b) // type:int64 var animal cat reflectType(animal)//type:main.cat } |
type name和type kind
? 在反射中关于类型还划分为两种:
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 | package main import ( "fmt" "reflect" ) type myInt int64 func reflectType(x interface{}) { t := reflect.TypeOf(x) fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind()) } func main() { var a *float32 // 指针 var b myInt // 自定义类型 var c rune // 类型别名 reflectType(a) // type: kind:ptr reflectType(b) // type:myInt kind:int64 reflectType(c) // type:int32 kind:int32 type person struct { name string age int } type book struct{ title string } var d = person{ name: "葫芦娃", age: 18, } var e = book{title: "《资治通鉴》"} reflectType(d) // type:person kind:struct reflectType(e) // type:book kind:struct } |
? Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的
ValueOf
?
? 下面是相关的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | func ValueOf(i interface{}) Value { if i == nil { return Value{} } // …… return unpackEface(i) } func unpackEface(i interface{}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} } |
? 在
? 在讲

? 通过
通过反射获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | func reflectValue(x interface{}) { v := reflect.ValueOf(x) k := v.Kind() switch k { case reflect.Int64: // v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换 fmt.Printf("type is int64, value is %d\n", int64(v.Int())) case reflect.Float32: // v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换 fmt.Printf("type is float32, value is %f\n", float32(v.Float())) case reflect.Float64: // v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换 fmt.Printf("type is float64, value is %f\n", float64(v.Float())) } } func main() { var a float32 = 3.14 var b int64 = 100 reflectValue(a) // type is float32, value is 3.140000 reflectValue(b) // type is int64, value is 100 // 将int类型的原始值转换为reflect.Value类型 c := reflect.ValueOf(10) fmt.Printf("type c :%T\n", c) // type c :reflect.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 | package main import ( "fmt" "reflect" ) func reflectSetValue1(x interface{}) { v := reflect.ValueOf(x) if v.Kind() == reflect.Int64 { v.SetInt(200) //修改的是副本,reflect包会引发panic } } func reflectSetValue2(x interface{}) { v := reflect.ValueOf(x) // 反射中使用 Elem()方法获取指针对应的值 if v.Elem().Kind() == reflect.Int64 { v.Elem().SetInt(200) } } func main() { var a int64 = 100 // reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value reflectSetValue2(&a) fmt.Println(a) } |
? 说到这里要提一下Go官方博客中的反射三大定律:
1.Reflection goes from interface value to reflection object.
2.Reflection goes from reflection object to interface value.
3.To modify a reflection object, the value must be settable.
? 前两条还比较好理解,通过
? 第三条耗尽洪荒之力可能也不太好理解了,典型明明英文读的懂就是不知道它在说啥。官方啥意思呢,先翻译一下就是你要是想操作反射变量,那么这个值必须是可设置的。啥叫可设置的呢,我们原来有个变量A(就是原变量),然后通过反射拿到了它对应的反射变量B,我们对变量B的修改同时会影响原变量A,那么这就属于可设置的。
? 说的高级一点就是,
函数反射
? 不知道函数的参数是什么,或者入参类型很多,没有办法统一表示,那就用反射;
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 | package main import ( "reflect" "fmt" ) type Child struct { Name string Grade int Handsome bool } type Adult struct { ID string `qson:"Name"` Occupation string Handsome bool } // 如果输入参数 i 是 Slice,元素是结构体,有一个字段名为 `Handsome`, // 并且有一个字段的 tag 或者字段名是 `Name` , // 如果该 `Name` 字段的值是 `qcrao`, // 就把结构体中名为 `Handsome` 的字段值设置为 true。 func handsome(i interface{}) { // 获取 i 的反射变量 Value v := reflect.ValueOf(i) // 确定 v 是一个 Slice if v.Kind() != reflect.Slice { return } // 确定 v 是的元素为结构体 if e := v.Type().Elem(); e.Kind() != reflect.Struct { return } // 确定结构体的字段名含有 "ID" 或者 json tag 标签为 `name` // 确定结构体的字段名 "Handsome" st := v.Type().Elem() // 寻找字段名为 Name 或者 tag 的值为 Name 的字段 foundName := false for i := 0; i < st.NumField(); i++ { f := st.Field(i) tag := f.Tag.Get("qson") if (tag == "Name" || f.Name == "Name") && f.Type.Kind() == reflect.String { foundName = true break } } if !foundName { return } if niceField, foundHandsome := st.FieldByName("Handsome"); foundHandsome == false || niceField.Type.Kind() != reflect.Bool { return } // 设置名字为 "qcrao" 的对象的 "Handsome" 字段为 true for i := 0; i < v.Len(); i++ { e := v.Index(i) handsome := e.FieldByName("Handsome") // 寻找字段名为 Name 或者 tag 的值为 Name 的字段 var name reflect.Value for j := 0; j < st.NumField(); j++ { f := st.Field(j) tag := f.Tag.Get("qson") if tag == "Name" || f.Name == "Name" { name = v.Index(i).Field(j) } } if name.String() == "qcrao" { handsome.SetBool(true) } } } func main() { children := []Child{ {Name: "Ava", Grade: 3, Handsome: true}, {Name: "qcrao", Grade: 6, Handsome: false}, } adults := []Adult{ {ID: "Steve", Occupation: "Clerk", Handsome: true}, {ID: "qcrao", Occupation: "Go Programmer", Handsome: false}, } fmt.Printf("adults before handsome: %v\n", adults) handsome(adults) fmt.Printf("adults after handsome: %v\n", adults) fmt.Println("-------------") fmt.Printf("children before handsome: %v\n", children) handsome(children) fmt.Printf("children after handsome: %v\n", children) } |
output :
1 2 3 4 5 | adults before handsome: [{Steve Clerk true} {qcrao Go Programmer false}] adults after handsome: [{Steve Clerk true} {qcrao Go Programmer true}] ------------- children before handsome: [{Ava 3 true} {qcrao 6 false}] children after handsome: [{Ava 3 true} {qcrao 6 true}] |
结构体反射
与结构体相关的方法
? 任意值通过
StructField类型
?
?
1 2 3 4 5 6 7 8 9 10 11 | type StructField struct { // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。 // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers Name string PkgPath string Type Type // 字段的类型 Tag StructTag // 字段的标签 Offset uintptr // 字段在结构体中的字节偏移量 Index []int // 用于Type.FieldByIndex时的索引切片 Anonymous bool // 是否匿名字段 } |
结构体反射示例
? 当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | type student struct { Name string `json:"name"` Score int `json:"score"` } func main() { stu1 := student{ Name: "葫芦娃", Score: 90, } t := reflect.TypeOf(stu1) fmt.Println(t.Name(), t.Kind()) // student struct // 通过for循环遍历结构体的所有字段信息 for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json")) } // 通过字段名获取指定结构体字段信息 if scoreField, ok := t.FieldByName("Score"); ok { fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json")) } } |
? 接下来编写一个函数
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 | // 给student添加两个方法 Study和Sleep(注意首字母大写) func (s student) Study() string { msg := "好好学习,天天向上。" fmt.Println(msg) return msg } func (s student) Sleep() string { msg := "好好睡觉,天天长个。" fmt.Println(msg) return msg } func printMethod(x interface{}) { t := reflect.TypeOf(x) v := reflect.ValueOf(x) fmt.Println(t.NumMethod()) for i := 0; i < v.NumMethod(); i++ { methodType := v.Method(i).Type() fmt.Printf("method name:%s\n", t.Method(i).Name) fmt.Printf("method:%s\n", methodType) // 通过反射调用方法传递的参数必须是 []reflect.Value 类型 var args = []reflect.Value{} v.Method(i).Call(args) } } |
反射在GORM中的运用
? 选了一个用过的ORM框架,看看在GORM中都干了写啥事情,下面的代码主要是围绕从数据库中查询数据。简单说一下都干了啥:
- queryCallback 方法组成sql语句,调用database/sql 包中的query来查询,循环rows结果通过反射组成对象。
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 | //callback_query.go func queryCallback(scope *Scope) { if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip { return } //we are only preloading relations, dont touch base model if _, skip := scope.InstanceGet("gorm:only_preload"); skip { return } defer scope.trace(NowFunc()) var ( isSlice, isPtr bool resultType reflect.Type results = scope.IndirectValue() ) // 找到排序字段 if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok { if primaryField := scope.PrimaryField(); primaryField != nil { scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy)) } } if value, ok := scope.Get("gorm:query_destination"); ok { results = indirect(reflect.ValueOf(value)) } if kind := results.Kind(); kind == reflect.Slice { isSlice = true resultType = results.Type().Elem() results.Set(reflect.MakeSlice(results.Type(), 0, 0)) if resultType.Kind() == reflect.Ptr { isPtr = true resultType = resultType.Elem() } } else if kind != reflect.Struct { scope.Err(errors.New("unsupported destination, should be slice or struct")) return } // 准备查询语句 scope.prepareQuerySQL() if !scope.HasError() { scope.db.RowsAffected = 0 if str, ok := scope.Get("gorm:query_option"); ok { scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) } // 调用database/sql 包中的query来查询 if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { defer rows.Close() columns, _ := rows.Columns() // 循环rows 组成对象 for rows.Next() { scope.db.RowsAffected++ elem := results if isSlice { elem = reflect.New(resultType).Elem() } scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields()) if isSlice { if isPtr { //通过反射组成新的slice results.Set(reflect.Append(results, elem.Addr())) } else { results.Set(reflect.Append(results, elem)) } } } if err := rows.Err(); err != nil { scope.Err(err) } else if scope.db.RowsAffected == 0 && !isSlice { scope.Err(ErrRecordNotFound) } } } } //scope.go func (scope *Scope) prepareQuerySQL() { // 如果是原生语句 则组织sql语句 if scope.Search.raw { scope.Raw(scope.CombinedConditionSql()) } else { // 组织select 语句 // scope.selectSQL() 组织select 需要查询的字段 // scope.QuotedTableName() 获取表名 // scope.CombinedConditionSql()组织条件语句 scope.Raw(fmt.Sprintf("SELECT %v FROM %v %v", scope.selectSQL(), scope.QuotedTableName(), scope.CombinedConditionSql())) } return } |
为什么我们吐槽反射太慢
? 在一些博客中看到这样一段话(大意是反射效率不高),好奇宝宝的我照着Golang反射性能分析这篇博客简单测了一下,确实反射挺慢…
反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
小结
1.开篇的时候提到了不推荐使用
1 2 3 4 | // 设置切片的 len 字段,如果类型不是切片,就会panic func (v Value) SetLen(n int) //判断v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一,否则IsNil函数会导致panic。 func (v Value) IsNil() bool |
? 开头提到的深度相等函数,也是属于效率比较慢的,如果其他的判断方法无法满足要求,可以试一下深度相等大杀器。
2.通过看源码可以发现,基本上反射可以接受任意的interface{}类型,而对于interface{}其实也是需要去花点时间看看的,否则就像我一样一开始分不清
3.看到有的博客里说反射有点慢,但实际上前几天我做了一个结构体转map的测试,分别用json和反射实现的,反射就快很多,现在看来可能和其他方法比反射算快的也算慢的.写反射相关的总结的时候总算记得测试一下反射性能了.反射为什么慢呢.以给结构体的变量赋值为例.反射方法FieldByName(“B”)要去遍历结构体的字段,一一进行匹配找到我们需要的field,所以这么折腾就慢了好多.学到了…
1 2 3 4 5 6 7 8 | for i := 0; i < b.N; i++ { //通过FieledByName找到对应Field赋值 r = temp.FieldByName("B").SetInt(int64(2008)) } for i := 0; i < b.N; i++ { //直接找到对应field赋值 tf.B= i } |