學(xué)習(xí)使用Go反射的用法示例
什么是反射
大多數(shù)時(shí)候,Go中的變量,類(lèi)型和函數(shù)非常簡(jiǎn)單直接。當(dāng)需要一個(gè)類(lèi)型、變量或者是函數(shù)時(shí),可以直接定義它們:
type Foo struct { A int B string } var x Foo func DoSomething(f Foo) { fmt.Println(f.A, f.B) }
但是有時(shí)你希望在運(yùn)行時(shí)使用變量的在編寫(xiě)程序時(shí)還不存在的信息。比如你正在嘗試將文件或網(wǎng)絡(luò)請(qǐng)求中的數(shù)據(jù)映射到變量中。或者你想構(gòu)建一個(gè)適用于不同類(lèi)型的工具。在這種情況下,你需要使用反射。反射使您能夠在運(yùn)行時(shí)檢查類(lèi)型。它還允許您在運(yùn)行時(shí)檢查,修改和創(chuàng)建變量,函數(shù)和結(jié)構(gòu)體。
Go中的反射是基于三個(gè)概念構(gòu)建的:類(lèi)型,種類(lèi)和值(Types Kinds Values)。標(biāo)準(zhǔn)庫(kù)中的reflect包提供了 Go 反射的實(shí)現(xiàn)。
反射變量類(lèi)型
首先讓我們看一下類(lèi)型。你可以使用反射來(lái)調(diào)用函數(shù)varType := reflect.TypeOf(var)來(lái)獲取變量var的類(lèi)型。這將返回類(lèi)型為reflect.Type的變量,該變量具有獲取定義時(shí)變量的類(lèi)型的各種信息的方法集。下面我們來(lái)看一下常用的獲取類(lèi)型信息的方法。
我們要看的第一個(gè)方法是Name()。這將返回變量類(lèi)型的名稱(chēng)。某些類(lèi)型(例如切片或指針)沒(méi)有名稱(chēng),此方法會(huì)返回空字符串。
下一個(gè)方法,也是我認(rèn)為第一個(gè)真正非常有用的方法是Kind()。Type是由Kind組成的---Kind 是切片,映射,指針,結(jié)構(gòu),接口,字符串,數(shù)組,函數(shù),int或其他某種原始類(lèi)型的抽象表示。要理解Type和Kind之間的差異可能有些棘手,但是請(qǐng)你以這種方式來(lái)思考。如果定義一個(gè)名為Foo的結(jié)構(gòu)體,則Kind為struct,類(lèi)型為Foo。
使用反射時(shí)要注意的一件事:反射包中的所有內(nèi)容都假定你知道自己在做什么,并且如果使用不正確,許多函數(shù)和方法調(diào)用都會(huì)引起 panic。例如,如果你在reflect.Type上調(diào)用與當(dāng)前類(lèi)型不同的類(lèi)型關(guān)聯(lián)的方法,您的代碼將會(huì)panic。
如果變量是指針,映射,切片,通道或數(shù)組變量,則可以使用varType.Elem()找出指向或包含的值的類(lèi)型。
如果變量是結(jié)構(gòu)體,則可以使用反射來(lái)獲取結(jié)構(gòu)體中的字段數(shù),并從每個(gè)字段上獲取reflect.StructField結(jié)構(gòu)體。 reflection.StructField為您提供了字段的名稱(chēng),標(biāo)號(hào),類(lèi)型和結(jié)構(gòu)體標(biāo)簽。其中標(biāo)簽信息對(duì)應(yīng)reflect.StructTag類(lèi)型的字符串,并且它提供了Get方法用于解析和根據(jù)特定key提取標(biāo)簽信息中的子串。
下面是一個(gè)簡(jiǎn)單的示例,用于輸出各種變量的類(lèi)型信息:
type Foo struct { A int `tag1:"First Tag" tag2:"Second Tag"` B string } func main() { sl := []int{1, 2, 3} greeting := "hello" greetingPtr := &greeting f := Foo{A: 10, B: "Salutations"} fp := &f slType := reflect.TypeOf(sl) gType := reflect.TypeOf(greeting) grpType := reflect.TypeOf(greetingPtr) fType := reflect.TypeOf(f) fpType := reflect.TypeOf(fp) examiner(slType, 0) examiner(gType, 0) examiner(grpType, 0) examiner(fType, 0) examiner(fpType, 0) } func examiner(t reflect.Type, depth int) { fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind()) switch t.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice: fmt.Println(strings.Repeat("\t", depth+1), "Contained type:") examiner(t.Elem(), depth+1) case reflect.Struct: for i := 0; i < t.NumField(); i++ { f := t.Field(i) fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind()) if f.Tag != "" { fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag) fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2")) } } } }
變量的類(lèi)型輸出如下:
Type is and kind is slice Contained type: Type is int and kind is int Type is string and kind is string Type is and kind is ptr Contained type: Type is string and kind is string Type is Foo and kind is struct Field 1 name is A type is int and kind is int Tag is tag1:"First Tag" tag2:"Second Tag" tag1 is First Tag tag2 is Second Tag Field 2 name is B type is string and kind is string Type is and kind is ptr Contained type: Type is Foo and kind is struct Field 1 name is A type is int and kind is int Tag is tag1:"First Tag" tag2:"Second Tag" tag1 is First Tag tag2 is Second Tag Field 2 name is B type is string and kind is string
Run in go playground: https://play.golang.org/p/lZ97yAUHxX
使用反射創(chuàng)建新實(shí)例
除了檢查變量的類(lèi)型外,還可以使用反射來(lái)讀取,設(shè)置或創(chuàng)建值。首先,需要使用refVal := reflect.ValueOf(var) 為變量創(chuàng)建一個(gè)reflect.Value實(shí)例。如果希望能夠使用反射來(lái)修改值,則必須使用refPtrVal := reflect.ValueOf(&var);獲得指向變量的指針。如果不這樣做,則可以使用反射來(lái)讀取該值,但不能對(duì)其進(jìn)行修改。
一旦有了reflect.Value實(shí)例就可以使用Type()方法獲取變量的reflect.Type。
如果要修改值,請(qǐng)記住它必須是一個(gè)指針,并且必須首先對(duì)其進(jìn)行解引用。使用refPtrVal.Elem().Set(newRefVal)來(lái)修改值,并且傳遞給Set()的值也必須是reflect.Value。
如果要?jiǎng)?chuàng)建一個(gè)新值,可以使用函數(shù)newPtrVal := reflect.New(varType)來(lái)實(shí)現(xiàn),并傳入一個(gè)reflect.Type。這將返回一個(gè)指針值,然后可以像上面那樣使用Elem().Set()對(duì)其進(jìn)行修改。
最后,你可以通過(guò)調(diào)用Interface()方法從reflect.Value回到普通變量值。由于Go沒(méi)有泛型,因此變量的原始類(lèi)型會(huì)丟失;該方法返回類(lèi)型為interface{}的值。如果創(chuàng)建了一個(gè)指針以便可以修改該值,則需要使用Elem().Interface()解引用反射的指針。在這兩種情況下,都需要將空接口轉(zhuǎn)換為實(shí)際類(lèi)型才能使用它。
下面的代碼來(lái)演示這些概念:
type Foo struct { A int `tag1:"First Tag" tag2:"Second Tag"` B string } func main() { greeting := "hello" f := Foo{A: 10, B: "Salutations"} gVal := reflect.ValueOf(greeting) // not a pointer so all we can do is read it fmt.Println(gVal.Interface()) gpVal := reflect.ValueOf(&greeting) // it's a pointer, so we can change it, and it changes the underlying variable gpVal.Elem().SetString("goodbye") fmt.Println(greeting) fType := reflect.TypeOf(f) fVal := reflect.New(fType) fVal.Elem().Field(0).SetInt(20) fVal.Elem().Field(1).SetString("Greetings") f2 := fVal.Elem().Interface().(Foo) fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B) }
他們的輸出如下:
hello goodbye {A:20 B:Greetings}, 20, Greetings
Run in go playground https://play.golang.org/p/PFcEYfZqZ8
反射創(chuàng)建引用類(lèi)型的實(shí)例
除了生成內(nèi)置類(lèi)型和用戶(hù)定義類(lèi)型的實(shí)例之外,還可以使用反射來(lái)生成通常需要make函數(shù)的實(shí)例??梢允褂胷eflect.MakeSlice,reflect.MakeMap和reflect.MakeChan函數(shù)制作切片,Map或通道。在所有情況下,都提供一個(gè)reflect.Type,然后獲取一個(gè)reflect.Value,可以使用反射對(duì)其進(jìn)行操作,或者可以將其分配回一個(gè)標(biāo)準(zhǔn)變量。
func main() { // 定義變量 intSlice := make([]int, 0) mapStringInt := make(map[string]int) // 獲取變量的 reflect.Type sliceType := reflect.TypeOf(intSlice) mapType := reflect.TypeOf(mapStringInt) // 使用反射創(chuàng)建類(lèi)型的新實(shí)例 intSliceReflect := reflect.MakeSlice(sliceType, 0, 0) mapReflect := reflect.MakeMap(mapType) // 將創(chuàng)建的新實(shí)例分配回一個(gè)標(biāo)準(zhǔn)變量 v := 10 rv := reflect.ValueOf(v) intSliceReflect = reflect.Append(intSliceReflect, rv) intSlice2 := intSliceReflect.Interface().([]int) fmt.Println(intSlice2) k := "hello" rk := reflect.ValueOf(k) mapReflect.SetMapIndex(rk, rv) mapStringInt2 := mapReflect.Interface().(map[string]int) fmt.Println(mapStringInt2) }
使用反射創(chuàng)建函數(shù)
反射不僅僅可以為存儲(chǔ)數(shù)據(jù)創(chuàng)造新的地方。還可以使用reflect.MakeFunc函數(shù)使用reflect來(lái)創(chuàng)建新函數(shù)。該函數(shù)期望我們要?jiǎng)?chuàng)建的函數(shù)的reflect.Type,以及一個(gè)閉包,其輸入?yún)?shù)為[]reflect.Value類(lèi)型,其返回類(lèi)型也為[] reflect.Value類(lèi)型。下面是一個(gè)簡(jiǎn)單的示例,它為傳遞給它的任何函數(shù)創(chuàng)建一個(gè)定時(shí)包裝器:
func MakeTimedFunction(f interface{}) interface{} { rf := reflect.TypeOf(f) if rf.Kind() != reflect.Func { panic("expects a function") } vf := reflect.ValueOf(f) wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value { start := time.Now() out := vf.Call(in) end := time.Now() fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start)) return out }) return wrapperF.Interface() } func timeMe() { fmt.Println("starting") time.Sleep(1 * time.Second) fmt.Println("ending") } func timeMeToo(a int) int { fmt.Println("starting") time.Sleep(time.Duration(a) * time.Second) result := a * 2 fmt.Println("ending") return result } func main() { timed := MakeTimedFunction(timeMe).(func()) timed() timedToo := MakeTimedFunction(timeMeToo).(func(int) int) fmt.Println(timedToo(2)) }
你可以在goplayground運(yùn)行代碼https://play.golang.org/p/QZ8ttFZzGx并看到輸出如下:
starting ending calling main.timeMe took 1s starting ending calling main.timeMeToo took 2s 4
反射是每個(gè)Go開(kāi)發(fā)人員都應(yīng)了解并學(xué)會(huì)的強(qiáng)大工具。但是使用他們可以用來(lái)做什么呢?在下一篇博客文章中,我將探討現(xiàn)有庫(kù)中反射的一些用法,并使用反射來(lái)創(chuàng)建一些新的東西。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Go語(yǔ)言反射獲取類(lèi)型屬性和方法示例
- Golang學(xué)習(xí)之反射機(jī)制的用法詳解
- Go語(yǔ)言學(xué)習(xí)之反射的用法詳解
- Go語(yǔ)言基礎(chǔ)反射示例詳解
- golang 如何通過(guò)反射創(chuàng)建新對(duì)象
- golang通過(guò)反射設(shè)置結(jié)構(gòu)體變量的值
- 圖文詳解go語(yǔ)言反射實(shí)現(xiàn)原理
- Go系列教程之反射的用法
- Go語(yǔ)言學(xué)習(xí)筆記之反射用法詳解
- Go語(yǔ)言中反射的正確使用
- Go語(yǔ)言中使用反射的方法
- Go語(yǔ)言的反射機(jī)制詳解
相關(guān)文章
golang如何實(shí)現(xiàn)三元運(yùn)算符功能
這篇文章主要介紹了在其他一些編程語(yǔ)言中,如?C?語(yǔ)言,三元運(yùn)算符是一種可以用一行代碼實(shí)現(xiàn)條件選擇的簡(jiǎn)便方法,那么在Go語(yǔ)言中如何實(shí)現(xiàn)類(lèi)似功能呢,下面就跟隨小編一起學(xué)習(xí)一下吧2024-02-02詳解如何使用unsafe標(biāo)準(zhǔn)庫(kù)突破Golang中的類(lèi)型限制
在使用c語(yǔ)言編程時(shí),常常因?yàn)轭?lèi)型的問(wèn)題大傷腦筋,而,golang提供了一些方式用于喜歡hack的用戶(hù),下面我們就來(lái)講講如何使用unsafe標(biāo)準(zhǔn)庫(kù)突破Golang中的類(lèi)型限制吧2024-03-03Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn)
本文主要介紹了Gin+Gorm實(shí)現(xiàn)CRUD的實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Go高效率開(kāi)發(fā)Web參數(shù)校驗(yàn)三種方式實(shí)例
這篇文章主要介紹了Go高效率開(kāi)發(fā)Web參數(shù)校驗(yàn)三種方式實(shí)例,需要的朋友可以參考下2022-11-11Golang設(shè)計(jì)模式之單例模式詳細(xì)講解
單例模式很容易記住。就像名稱(chēng)一樣,它只能提供對(duì)象的單一實(shí)例,保證一個(gè)類(lèi)只有一個(gè)實(shí)例,并提供一個(gè)全局訪(fǎng)問(wèn)該實(shí)例的方法。本文就來(lái)聊聊Go語(yǔ)言中的單例模式,感興趣的小伙伴可以了解一下2023-01-01