Golang 反射操作整理
前言
反射是什么? 我們平常也是經(jīng)常用到, 而且這名詞都用爛了, 這里就不再詳細(xì)介紹了.
簡單說, 就是有一個(gè)不知道是什么類型的變量, 通過反射可以獲取其類型, 并可操作屬性和方法.
反射的用途一般是用作生成工具方法, 比如你需要一個(gè)ToString方法, 要將變量轉(zhuǎn)為字符串類型, 如果沒有反射, 就需要寫: ToStringInt, ToStringBool…等等, 每一個(gè)類型都要加一個(gè)方法. 而有了反射, 只需要一個(gè)ToString方法, 不管是什么類型的變量, 都扔給他就好啦.
對于PHP這種弱類型的語言來說, 如果要調(diào)用變量$a的$b方法, 只需要$a->$b()即可. 而對于Golang這種強(qiáng)類型的語言就不能這么隨意了. 故下面簡單介紹一下Golang中反射的應(yīng)用.
希望看完反射的用法之后, 至少以后再看相關(guān)代碼不至于一臉懵逼. 甚至于需要的時(shí)候還能自己手?jǐn)]一套.
使用
Golang中反射的操作定義在包reflect中. 此包中主要包括以下兩個(gè)對象:
- reflect.Type 用于獲取變量的類型信息
- reflect.Value 用于對變量的值進(jìn)行操作
官方文檔地址: reflect.Type reflect.Value
我們在反射中的使用, 也是基于這兩個(gè)對象的.
對于反射的使用來說, 其實(shí)我們在平常使用中, 主要也就用到下面這幾種操作, 大部分復(fù)雜的操作百年難得一用:
- 變量類型和屬性的操作
- 變量方法的操作
下面就基于這兩種操作進(jìn)行簡單演示.
變量類型和屬性的操作
獲取屬性信息
反射中, 類型信息通過reflect.Type對象獲取.
獲取類型
u := struct {name string }{} // 獲取反射對象信息. 既 reflect.Type 對象 typeOf := reflect.TypeOf(u) fmt.Println(typeOf) // 變量的類型名 User fmt.Println(typeOf.Name()) // 獲取變量的底層類型. // 基礎(chǔ)類型的底層類型就是它本身, 比如 int // 而所有的自定義結(jié)構(gòu)體, 底層類型都是 struct // 所有的指針, 底層類型都是 ptr // golang 的所有底層類型定義在 reflect/type.go 文件中. // 可通過 reflect.Array 常量進(jìn)行定位 fmt.Println(typeOf.Kind())但是, 別高興的太早, 如果將變量u換成一個(gè)指針: u := &User{}. 使用上述方法就拿不到變量的類型了. 因?yàn)樽兞績?nèi)容存儲的是地址, 所以需要對該地址進(jìn)行取值操作. 反射包中的取值操作方法為: reflect.TypeOf(u).Elem(). 拿到值后, 就都一樣啦.
不止是指針, 包括: Array, Chan, Map, Ptr, Slice等, 其實(shí)存儲的都是地址, 故都需要進(jìn)行取值操作.
注意, 這里的底層類型Kind太有用了, 在通過反射處理的時(shí)候, 用戶自定義類型太多了根本判斷不過來, 但是其底層類型Kind一共就十幾個(gè), 相同底層類型的結(jié)構(gòu)就可以使用相同的處理方式了.
結(jié)構(gòu)體
type User struct {Gender int } u := struct {name stringAge intAddress struct {City stringCountry string} `json:"address"`User }{}/* 獲取反射對象信息 */ typeOf := reflect.TypeOf(u) // 結(jié)構(gòu)體字段的數(shù)量 fmt.Println(typeOf.NumField()) // 獲取第0個(gè)字段的信息, 返回 StructField 對象 (此對象下方有說明) // 可拿到字段的 Name 等信息, 包括字段的 Type 對象 fmt.Println(typeOf.Field(0)) // 根據(jù)變量名稱獲取字段 fmt.Println(typeOf.FieldByName("name")) // 獲取第2個(gè)結(jié)構(gòu)提的第0個(gè)元素 fmt.Println(typeOf.FieldByIndex([]int{2, 0}))/* StructField 字段對象內(nèi)容 */ structField := typeOf.Field(2) // 字段名 fmt.Println(structField.Name) // 字段的可訪問的包名 // 大寫字母打頭的公共字段, 都可以訪問, 故此值為空 fmt.Println(structField.PkgPath) // reflect.Type 對象 fmt.Println(structField.Type) // 字段的標(biāo)記字符串, 就是后面跟著的 `` 字符串 // 返回 StructTag 對象, 下方有說明 fmt.Println(structField.Tag) // 字段在結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)中偏移量, 字節(jié) fmt.Println(structField.Offset) // 字段在結(jié)構(gòu)體中的索引 fmt.Println(structField.Index) // 匿名字段. 結(jié)構(gòu)體中的 Gender 就屬于匿名字段 fmt.Println(structField.Anonymous)/* StructTag 標(biāo)簽內(nèi)容 */ tag := structField.Tag // 獲取指定名稱的標(biāo)簽值, 若不存在, 返回空字符串 fmt.Println(tag.Get("json")) // 與 Get 方法區(qū)別是, 第二個(gè)參數(shù)會返回若標(biāo)簽是否存在存在 // 有些標(biāo)簽的空字符串與未定義行為不同時(shí), 可使用此方法獲取 fmt.Println(tag.Lookup("json"))數(shù)組
切片的底層類型為Slice, 但不同切片中存儲的對象類型是不一樣的.
說白了, 數(shù)組其實(shí)就是一個(gè)指向首地址的指針嘛. 故要想獲取數(shù)組元素的內(nèi)容, 做一次取值操作就可以啦.
l := []int{1, 2}typeOf := reflect.TypeOf(l) // 空, 這里為什么空上面說過了, 數(shù)組是一個(gè)指針 fmt.Println(typeOf.Name()) // slice fmt.Println(typeOf.Kind()) // 獲取數(shù)組元素的類型 fmt.Println(typeOf.Elem().Kind()) fmt.Println(typeOf.Elem().Name())如果數(shù)組中存放的是結(jié)構(gòu)體, 在用作結(jié)構(gòu)體處理就好啦
map
m := map[string]int{"a": 1, } typeOf := reflect.TypeOf(m) // map 不使用取值也可以打印名字 map[string]int 不懂 fmt.Println(typeOf.Name()) // 對象底層類型. map fmt.Println(typeOf.Kind()) // 獲取 map 的 key 的類型 fmt.Println(typeOf.Key().Kind()) // 獲取 map value 的類型 fmt.Println(typeOf.Elem().Kind())獲取屬性值
反射中, 對于值的操作, 都是通過reflect.Value對象實(shí)現(xiàn)的, 此對象通過reflect.ValueOf獲取.
同時(shí), 所有的Value對象都可以調(diào)用Interface方法, 來將其轉(zhuǎn)回Interface{}對象, 然后再通過類型斷言進(jìn)行轉(zhuǎn)換.
基礎(chǔ)類型
基礎(chǔ)類型的取值, GO提供了對應(yīng)的方法, 使用起來也很簡單.
// 基礎(chǔ)類型取值 a := int64(3) valueOf := reflect.ValueOf(&a) // 取基礎(chǔ)類型. // 注意, 若不是相關(guān)類型, 會報(bào)錯. 可查看源碼 // 所有的整形, 都返回 int64, 若需要 int32, 可拿到返回值后強(qiáng)轉(zhuǎn) fmt.Println(valueOf.Int()) //fmt.Println(valueOf.Float()) //fmt.Println(valueOf.Uint()) // ... 等等結(jié)構(gòu)體
如果是自定義的結(jié)構(gòu)體怎么取值呢? 這, 一直找到基礎(chǔ)類型. 因?yàn)樗械淖远x結(jié)構(gòu)體都是有基礎(chǔ)類型組成的嘛.
u := struct { Name string Age int }{"xiao ming", 20} valueOf := reflect.ValueOf(u) fmt.Println(valueOf.Field(0).String())數(shù)組
如果是數(shù)組呢? 也很簡單
l := []int{1, 2, 3}valueOf := reflect.ValueOf(l) // 修改指定索引的值 fmt.Println(valueOf.Elem().Index(0)) // 獲取數(shù)組長度 fmt.Println(valueOf.Elem().Len())map
通過反射獲取Map的值時(shí), 取到的是Value對象, 同時(shí)要使用Value對象進(jìn)行取值. 畢竟Map的key和value類型都是不固定的嘛.
m := map[string]string{ "a": "1", } valueOf := reflect.ValueOf(m) // 獲取指定索引的值 fmt.Println(valueOf.MapIndex(reflect.ValueOf("a"))) // 若指定索引的值不存在, 會返回一個(gè) kind 為 Invalid 的 Value 對象 fmt.Println(valueOf.MapIndex(reflect.ValueOf("c"))) // 取 map 大小 fmt.Println(valueOf.Len()) // 獲取 map 的所有 key, 返回 Value 對象列表 fmt.Println(valueOf.MapKeys()) // 遍歷 map 用的迭代器 mapIter := valueOf.MapRange() mapIter.Next() // 將迭代指針直線下一個(gè), 返回是否還有數(shù)據(jù) fmt.Println(mapIter.Value()) fmt.Println(mapIter.Key())屬性賦值
基礎(chǔ)類型的賦值, reflect.Value對象提供了相關(guān)方法, 都以 Set 開頭.
這里注意, 只有指針類型的的變量才能被賦值. 其實(shí)很好理解, 值類型在方法調(diào)用時(shí)是通過復(fù)制傳值的. 只有傳遞指針才能夠找到原始值的內(nèi)存地址進(jìn)行修改.
故, 我們在賦值之前, 要調(diào)用Kind方法對其類型進(jìn)行判斷, 若不是通過指針創(chuàng)建的Value對象, 一定不能賦值.
以下所有的賦值操作, 都可以與取值操作聯(lián)動進(jìn)行.
基礎(chǔ)類型
a := int64(3) valueOf := reflect.ValueOf(a) // 此方法用于判斷 Value 對象是否可以賦值 valueOf.CanSet() // 因?yàn)槭侵羔? 所以這里需要進(jìn)行取值操作 valueOf.Elem().SetInt(20) fmt.Println(a)結(jié)構(gòu)體
結(jié)構(gòu)體的賦值與上面的獲取屬性值相同, 使用指針獲取Value對象, 然后對其基礎(chǔ)類型賦值.
需要注意的一點(diǎn), 結(jié)構(gòu)體只有公共字段才能夠通過反射進(jìn)行賦值, 若賦值給一個(gè)私有字段, 會拋出異常.
u := struct {Name stringAge int }{"xiao ming", 20}valueOf := reflect.ValueOf(&u) valueOf.Elem().Field(0).SetString("xiao hei") fmt.Println(u)數(shù)組
其基礎(chǔ)的Set方法確實(shí)提供了很多, 但是我查了一圈, 對于數(shù)組類型怎么賦值呢? 于是我看到了這個(gè)方法:
func (v Value) Set(x Value)
這個(gè)Set方法, 接收的參數(shù)是Value對象? 那不就成了么. 注意, Set是直接進(jìn)行替換, 而不是追加.
l := []int{1, 2, 3}valueOf := reflect.ValueOf(&l) // 創(chuàng)建一個(gè)數(shù)組用于后面進(jìn)行賦值 // 注意, 數(shù)組類型要相同 setValueOf := reflect.ValueOf([]int{4, 5}) valueOf.Elem().Set(setValueOf) fmt.Println(l)// 修改指定索引的值 // 通過指針, 將指定索引的值取出來后, 進(jìn)行賦值 valueOf.Elem().Index(0).SetInt(9) fmt.Println(l)map
m := map[string]string{"a": "1", } valueOf := reflect.ValueOf(&m) // 給指定的 key 設(shè)置 valueOf.Elem().SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf("2")) fmt.Println(m)創(chuàng)建空值 Value
除了上面的賦值操作, 還有一種不需要判斷對象類型的方式, 通過方法New, 可以創(chuàng)建一個(gè)相同類型的空值Value對象, 返回的是一個(gè)指針的Value類型.
此操作的好處是, 在使用過程中, 完全不需要判斷對象類型.
a := int64(3) // 創(chuàng)建一個(gè)相同類型內(nèi)容. 返回的是一個(gè)指針 fmt.Println(reflect.New(reflect.TypeOf(a)).Elem())變量方法的操作
普通方法
普通方法指未依附與結(jié)構(gòu)體的方法.
func add(a, b int) int {return a + b }func main() {valueOf := reflect.ValueOf(add)// 構(gòu)造函數(shù)參數(shù)paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}// 調(diào)用函數(shù). 返回一個(gè) Value 數(shù)組retList := valueOf.Call(paramList)// 獲取返回值fmt.Println(retList[0].Int()) }結(jié)構(gòu)體方法
獲取方法信息
這里需要注意, 結(jié)構(gòu)體指針和對象所擁有的方法數(shù)量是不同的, 具體可看: https://hujingnb.com/archives/348
type User struct {Name string }func (u User) GetName() string {return u.Name }func (u *User) SetName(name string) {u.Name = name }func main() {u := User{}typeOf := reflect.TypeOf(&u)// 獲取結(jié)構(gòu)體中方法數(shù)量. 私有方法是拿不到的fmt.Println(typeOf.NumMethod())// 獲取第0個(gè)方法, 返回 Method 對象. 下面介紹fmt.Println(typeOf.Method(0))// 根據(jù)方法名獲取, 返回 Method 對象fmt.Println(typeOf.MethodByName("GetName"))/* Method 對象 */setNameFunc, _ := typeOf.MethodByName("GetName")// 方法名fmt.Println(setNameFunc.Name)// 方法簽名fmt.Println(setNameFunc.Type)fmt.Println(setNameFunc.Index)// 字段的可訪問的包名. 公共方法為空fmt.Println(setNameFunc.PkgPath) }方法調(diào)用
type User struct {Name string }func (u User) GetName() string {return u.Name }func (u *User) SetName(name string) {u.Name = name }func main() {u := User{}valueOf := reflect.ValueOf(&u)// 獲取結(jié)構(gòu)體中方法數(shù)量. 私有方法是拿不到的fmt.Println(valueOf.NumMethod())// 獲取第0個(gè)方法, 返回 Method 對象. 下面介紹fmt.Println(valueOf.Method(0))// 根據(jù)方法名獲取, 返回 Method 對象fmt.Println(valueOf.MethodByName("GetName"))/* Method 對象 */setNameFunc := valueOf.MethodByName("SetName")// 調(diào)用方法params := []reflect.Value{reflect.ValueOf("xiao ming")}setNameFunc.Call(params)// 此時(shí)對象的值已經(jīng)改變fmt.Println(u)// 接收方法返回值getNameFunc := valueOf.MethodByName("GetName")fmt.Println(getNameFunc.Call([]reflect.Value{})) }原文地址: https://hujingnb.com/archives/676
總結(jié)
以上是生活随笔為你收集整理的Golang 反射操作整理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过gparted 调整 ubuntu
- 下一篇: 使用java实现面向对象编程第二章_ja