Golang反射-下篇
本文是Golang反射-上篇的續篇內容,主要介紹反射實際的一些使用
1、判斷型別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能傳入所有型別,是因為所有的型別都實現了空介面
package main import ( "fmt" "reflect" ) func main() { var s interface{} = "abc" //TypeOf會返回目標的物件 reflectType:=reflect.TypeOf(s) reflectValue:=reflect.ValueOf(s) fmt.Printf("[typeof:%v]\n", reflectType) // string fmt.Printf("[valueof:%v]\n", reflectValue) // abc }
2、自定義struct的反射
自定義struct的相關操作
-
對於成員變數
- 先獲取interface的reflect.Type,然後遍歷NumField
- 再通過reflect.Type的Field獲取欄位名及型別
- 最後通過Field的interface獲取對應的value
-
對於方法
- 先獲取interface的reflect.Type,然後遍歷NumMethod
- 再通過reflect.Type的t.Method獲取真實的方法名
- 最後通過Name和Type獲取方法的型別和值
注意點
- 用於對未知型別進行遍歷探測其Field,抽象成一個函式
- go語言裡面struct成員變數小寫,在反射的時候直接panic()
- 結構體方法名小寫是不會panic的,反射值也不會被檢視到
- 指標方法是不能被反射檢視到的
package main import ( "fmt" "reflect" ) type Person struct { Name string Age int } type Student struct { Person // 匿名結構體巢狀 StudentId int SchoolName string Graduated bool Hobbies []string //panic: reflect.Value.Interface: cannot return value obtained from unexported field or method //hobbies []string Label map[string]string } func (s *Student) GoHome() { fmt.Printf("回家了,sid:%d\n", s.StudentId) } //func (s Student) GoHome() { // fmt.Printf("回家了,sid:%d\n", s.StudentId) //} func (s Student) GotoSchool() { fmt.Printf("上學了,sid:%d\n", s.StudentId) } func (s *Student) graduated() { fmt.Printf("畢業了,sid:%d\n", s.StudentId) } //func (s Student) Ggraduated() { // fmt.Printf("畢業了,sid:%d\n", s.StudentId) //} func reflectProbeStruct(s interface{}) { // 獲取目標物件 t := reflect.TypeOf(s) fmt.Printf("物件的型別名稱 %s\n", 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 { fmt.Printf("匿名欄位 第 %d 個欄位,欄位名 %s, 欄位型別 %v, 欄位的值 %v\n", i+1, key.Name, key.Type, value) } else { fmt.Printf("命名欄位 第 %d 個欄位,欄位名 %s, 欄位型別 %v, 欄位的值 %v\n", i+1, key.Name, key.Type, value) } } // 列印方法 for i := 0; i < t.NumMethod(); i++ { m := t.Method(i) fmt.Printf("第 %d 個方法,方法名 %s, 方法型別 %v\n", i+1, m.Name, m.Type) } } func main() { s := Student{ Person: Person{ "geek", 24, }, StudentId: 123, SchoolName: "Beijing University", Graduated: true, Hobbies: []string{"唱", "跳", "Rap"}, //hobbies: []string{"唱", "跳", "Rap"}, Label: map[string]string{"k1": "v1", "k2": "v2"}, } p := Person{ Name: "張三", Age: 100, } reflectProbeStruct(s) reflectProbeStruct(p) /* 物件的型別名稱 Student 匿名欄位 第 1 個欄位,欄位名 Person, 欄位型別 main.Person, 欄位的值 {geek 24} 命名欄位 第 2 個欄位,欄位名 StudentId, 欄位型別 int, 欄位的值 123 命名欄位 第 3 個欄位,欄位名 SchoolName, 欄位型別 string, 欄位的值 Beijing University 命名欄位 第 4 個欄位,欄位名 Graduated, 欄位型別 bool, 欄位的值 true 命名欄位 第 5 個欄位,欄位名 Hobbies, 欄位型別 []string, 欄位的值 [唱 跳 Rap] 命名欄位 第 6 個欄位,欄位名 Label, 欄位型別 map[string]string, 欄位的值 map[k1:v1 k2:v2] 第 1 個方法,方法名 GotoSchool, 方法型別 func(main.Student) 物件的型別名稱 Person 命名欄位 第 1 個欄位,欄位名 Name, 欄位型別 string, 欄位的值 張三 命名欄位 第 2 個欄位,欄位名 Age, 欄位型別 int, 欄位的值 100 */ }
3、結構體標籤和反射
- json的標籤解析出json
- yaml的標籤解析出yaml
- xorm、gorm的標籤標識資料庫db欄位
- 自定義標籤
- 原理是t.Field.Tag.Lookup("標籤名")
示例
package main import ( "encoding/json" "fmt" "gopkg.in/yaml.v2" "io/ioutil" ) type Person struct { Name string `json:"name" yaml:"yaml_name"` Age int `json:"age" yaml:"yaml_age"` City string `json:"city" yaml:"yaml_city"` //City string `json:"-" yaml:"yaml_city"` // 忽略json:"-" } // json解析 func jsonWork() { // 物件Marshal成字串 p := Person{ Name: "geek", Age: 24, City: "Beijing", } data, err := json.Marshal(p) if err != nil { fmt.Printf("json.marshal.err: %v\n", err) } fmt.Printf("person.marshal.res: %v\n", string(data)) // 從字串解析成結構體 p2str := `{ "name": "張三", "age": 38, "city": "山東" }` var p2 Person err = json.Unmarshal([]byte(p2str), &p2) if err != nil { fmt.Printf("json.unmarshal.err: %v\n", err) return } fmt.Printf("person.unmarshal.res: %v\n", p2) } // yaml解析 func yamlWork() { filename := "a.yaml" content, err := ioutil.ReadFile(filename) if err != nil { fmt.Printf("ioutil.ReadFile.err: %v\n", err) return } p := &Person{} //err = yaml.Unmarshal([]byte(content), p) err = yaml.UnmarshalStrict([]byte(content), p) // 解析嚴格,考慮多餘欄位,忽略欄位等 if err != nil { fmt.Printf("yaml.UnmarshalStrict.err: %v\n", err) return } fmt.Printf("yaml.UnmarshalStrict.res: %v\n", p) } func main() { jsonWork() /* person.marshal.res: {"name":"geek","age":24,"city":"Beijing"} person.unmarshal.res: {張三 38 山東} */ yamlWork() /* yaml.UnmarshalStrict.res: &{李四 18 Shanghai} */ }
解析的yaml內容
yaml_name: 李四 yaml_age: 18 yaml_city: Shanghai
- 自定義標籤格式解析
package main import ( "fmt" "reflect" ) type Person struct { Name string `aa:"name"` Age int `aa:"age"` City string `aa:"city"` } // CustomParse 自定義解析 func CustomParse(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("aa");ok{ if tag == "-"{ continue } fmt.Printf("找到了aa標籤, key: %v, value: %v, tag: %s\n", key, value.Field(i), tag) } } } func main() { p := Person{ Name: "geek", Age: 24, City: "Beijing", } CustomParse(p) /* 找到了aa標籤, key: Name, value: geek, tag: name 找到了aa標籤, key: Age, value: 24, tag: age 找到了aa標籤, key: City, value: Beijing, tag: city */ }
4、反射呼叫函式
valueFunc := reflect.ValueOf(Add) //函式也是一種資料型別 typeFunc := reflect.TypeOf(Add) argNum := typeFunc.NumIn() //函式輸入引數的個數 args := make([]reflect.Value, argNum) //準備函式的輸入引數 for i := 0; i < argNum; i++ { if typeFunc.In(i).Kind() == reflect.Int { args[i] = reflect.ValueOf(3) //給每一個引數都賦3 } } sumValue := valueFunc.Call(args) //返回[]reflect.Value,因為go語言的函式返回可能是一個列表 if typeFunc.Out(0).Kind() == reflect.Int { sum := sumValue[0].Interface().(int) //從Value轉為原始資料型別 fmt.Printf("sum=%d\n", sum) }
5、反射呼叫方法
示例
user := User{ Id: 7, Name: "傑克遜", Weight: 65.5, Height: 1.68, } valueUser := reflect.ValueOf(&user) //必須傳指標,因為BMI()在定義的時候它是指標的方法 bmiMethod := valueUser.MethodByName("BMI") //MethodByName()通過Name返回類的成員變數 resultValue := bmiMethod.Call([]reflect.Value{}) //無引數時傳一個空的切片 result := resultValue[0].Interface().(float32) fmt.Printf("bmi=%.2f\n", result) //Think()在定義的時候用的不是指標,valueUser可以用指標也可以不用指標 thinkMethod := valueUser.MethodByName("Think") thinkMethod.Call([]reflect.Value{}) valueUser2 := reflect.ValueOf(user) thinkMethod = valueUser2.MethodByName("Think") thinkMethod.Call([]reflect.Value{})
過程
- 首先通過reflect.ValueOf(p1) 獲取得到反射型別物件
- reflect.ValueOf(p1).MethodByName需 要傳入準確的方法名稱(名稱不對會panic: reflect: call of reflect.Value.Call on zero Value),MethodByName代表註冊
- []reflect.Value 這是最終需要呼叫方法的引數,無引數傳空切片
- call呼叫
package main import ( "fmt" "reflect" ) type Person struct { Name string Age int Gender string } func (p Person) ReflectCallFuncWithArgs(name string, age int) { fmt.Printf("呼叫的是帶引數的方法, args.name: %s, args.age: %d, p.name: %s, p.age: %d\n", name, age, p.Name, p.Age, ) } func (p Person) ReflectCallFuncWithNoArgs() { fmt.Printf("呼叫的是不帶引數的方法\n") } func main() { p1 := Person{ Name: "geek", Age: 24, Gender: "男", } // 1.首先通過reflect.ValueOf(p1)獲取得到反射值型別 getValue := reflect.ValueOf(p1) // 2.帶引數的方法呼叫 methodValue1 := getValue.MethodByName("ReflectCallFuncWithArgs") // 引數是reflect.Value的切片 args1 := []reflect.Value{reflect.ValueOf("張三"), reflect.ValueOf(30)} methodValue1.Call(args1) // 3.不帶引數的方法呼叫 methodValue2 := getValue.MethodByName("ReflectCallFuncWithNoArgs") // 引數是reflect.Value的切片 args2 := make([]reflect.Value, 0) methodValue2.Call(args2) /* 呼叫的是帶引數的方法, args.name: 張三, args.age: 30, p.name: geek, p.age: 24 呼叫的是不帶引數的方法 */ }
6、反射建立值
6.1 反射建立struct
t := reflect.TypeOf(User{}) value := reflect.New(t) //根據reflect.Type建立一個物件,得到該物件的指標,再根據指標提到reflect.Value value.Elem().FieldByName("Id").SetInt(10) value.Elem().FieldByName("Name").SetString("宋江") value.Elem().FieldByName("Weight").SetFloat(78.) value.Elem().FieldByName("Height").SetFloat(168.4) user := value.Interface().(*User) //把反射型別轉成go原始資料型別 fmt.Printf("id=%d name=%s weight=%.1f height=%.1f\n", user.Id, user.Name, user.Weight, user.Height)
6.2 反射建立slice
var slice []User sliceType := reflect.TypeOf(slice) sliceValue := reflect.MakeSlice(sliceType, 1, 3) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc sliceValue.Index(0).Set(reflect.ValueOf(User{ Id: 8, Name: "李達", Weight: 80, Height: 180, })) users := sliceValue.Interface().([]User) fmt.Printf("1st user name %s\n", users[0].Name)
6.3 反射建立map
var userMap map[int]*User mapType := reflect.TypeOf(userMap) // mapValue:=reflect.MakeMap(mapType) mapValue := reflect.MakeMapWithSize(mapType, 10) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc user := &common.User{ Id: 7, Name: "傑克遜", Weight: 65.5, Height: 1.68, } key := reflect.ValueOf(user.Id) mapValue.SetMapIndex(key, reflect.ValueOf(user)) //SetMapIndex 往map裡新增一個key-value對 mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map userMap = mapValue.Interface().(map[int]*User) fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)
7、反射修改值
反射修改值要求必須是指標型別
修改值的操作:pointer.Elem().Setxxx()
package main import ( "fmt" "reflect" ) func main() { var num float64 = 3.14 fmt.Printf("原始值 %f\n", num) // 通過reflect.ValueOf獲取num中的value,必須是指標才可以修改值 //pointer := reflect.ValueOf(num) // 直接傳值會panic pointer := reflect.ValueOf(#) newValue := pointer.Elem() // 賦新的值 newValue.SetFloat(5.66) fmt.Printf("新的值 %f\n", num) }
7.1 反射修改struct
user := User{ Id: 7, Name: "傑克遜", Weight: 65.5, Height: 1.68, } valueUser := reflect.ValueOf(&user) // valueS.Elem().SetInt(8)//會panic valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通過Name返回類的成員變數。不能在指標Value上呼叫FieldByName addrValue := valueUser.Elem().FieldByName("addr") if addrValue.CanSet() { addrValue.SetString("北京") } else { fmt.Println("addr是未匯出成員,不可Set") //以小寫字母開頭的成員相當於是私有成員 }
7.2 反射修改slice
下面示例,間接的實現了append功能
users := make([]*User, 1, 5) //len=1,cap=5 sliceValue := reflect.ValueOf(&users) //準備通過Value修改users,所以傳users的地址 if sliceValue.Elem().Len() > 0 { //取得slice的長度 sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("哈哈哈") // u0 := users[0] fmt.Printf("1st user name change to %s\n", users[0].Name) } sliceValue.Elem().SetCap(3) //新的cap必須位於原始的len到cap之間 sliceValue.Elem().SetLen(2) //呼叫reflect.Value的Set()函式修改其底層指向的原始資料 sliceValue.Elem().Index(1).Set(reflect.ValueOf(&User{ Id: 8, Name: "geek", Weight: 80, Height: 180, })) fmt.Printf("2nd user name %s\n", users[1].Name)
7.3 反射修改map
u1 := &User{ Id: 7, Name: "傑克遜", Weight: 65.5, Height: 1.68, } u2 := &User{ Id: 8, Name: "傑克遜", Weight: 65.5, Height: 1.68, } userMap := make(map[int]*User, 5) userMap[u1.Id] = u1 mapValue := reflect.ValueOf(&userMap) //準備通過Value修改userMap,所以傳userMap的地址 mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2)) //SetMapIndex 往map裡新增一個key-value對 mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map for k, user := range userMap { fmt.Printf("key %d name %s\n", k, user.Name) }
See you ~
「其他文章」
- Gradle打包工具入門
- 服務網格和Istio初識-續
- 服務網格和Istio初識
- Golang與非對稱加密
- ack叢集Terway網路場景下的vSwitch擴容
- Golang與對稱加密
- 基於ack k8s叢集排程的方案設計
- 基於Dockerfile構建容器映象的最佳實踐
- Golang反射-下篇
- Golang反射-上篇
- Azure DevOps的使用入門
- Golang介面型別-下篇
- Golang介面型別-上篇
- 基於Python實現原生的登入驗證碼
- Golang開發命令列工具之flag包的使用
- Golang檔案操作-下篇
- k8s環境下處理容器時間問題的多種姿勢
- Golang基準測試
- 淺談Prometheus的資料儲存
- Golang單元測試