Go 学习笔记(39)— Go 反射
本文參考 http://c.biancheng.net/golang/reflect/
反射是把雙刃劍,功能強大但代碼可讀性并不理想,若非必要并不推薦使用反射。
1. 反射概念
反射是指在程序運行期對程序本身進行訪問和修改的能力。程序在編譯時,變量被轉換為內存地址,變量名不會被編譯器寫入到可執行部分。在運行程序時,程序無法獲取自身的信息。
支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱、類型信息、結構體信息等整合到可執行文件中,并給程序提供接口訪問反射信息,這樣就可以在程序運行期獲取類型的反射信息,并且有能力修改它們。
Go 語言中的反射是由 reflect 包提供支持的,它定義了兩個重要的類型 Type 和 Value 任意接口值在反射中都可以理解為由 reflect.Type 和 reflect.Value 兩部分組成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 兩個函數來獲取任意對象的 Value 和 Type 。
2. 獲取類型信息
在 Go 語言中通過調用 reflect.TypeOf 函數,我們可以從一個任何非接口類型的值創建一個 reflect.Type 值。 reflect.Type 值表示著此非接口值的類型。通過此值,我們可以得到很多此非接口類型的信息。
當然,我們也可以將一個接口值傳遞給一個 reflect.TypeOf 函數調用,但是此調用將返回一個表示著此接口值的動態類型的 reflect.Type 值。
實際上, reflect.TypeOf 函數的唯一參數的類型為 interface{} , reflect.TypeOf 函數將總是返回一個表示著此唯一接口參數值的動態類型的 reflect.Type 值。
使用 reflect.TypeOf() 函數可以獲得任意值的類型對象( reflect.Type ),程序通過類型對象可以訪問任意值的類型信息。下面通過例子來理解獲取類型對象的過程:
package mainimport ("fmt""reflect"
)func main() {var a int// 通過 reflect.TypeOf() 取得變量 a 的類型對象 typeOfA,類型為 reflect.Type()typeOfA := reflect.TypeOf(a)// 通過 typeOfA 類型對象的成員函數,可以分別獲取到 typeOfA 變量的類型名為 int,種類(Kind)為 intfmt.Println(typeOfA.Name(), typeOfA.Kind()) // int int}
func main() {s := "hello"sv := reflect.ValueOf(s)st := reflect.TypeOf(s)fmt.Println(sv, st) // hello string
}
2.1 反射的類型(Type)與種類(Kind)
在使用反射時,需要首先理解類型( Type )和種類( Kind )的區別。編程中,使用最多的是類型,但在反射中,當需要區分一個大品種的類型時,就會用到種類( Kind )。例如,需要統一判斷類型中的指針時,使用種類( Kind )信息就較為方便。
反射種類(Kind)的定義:
Go 程序中的類型( Type )指的是系統原生數據類型,如 int 、 string 、 bool 、 float32 等類型,以及使用 type 關鍵字定義的類型,這些類型的名稱就是其類型本身的名稱。例如使用 type A struct{} 定義結構體時,A 就是 struct{} 的類型。
種類( Kind )指的是對象歸屬的品種,在 reflect 包中有如下定義:
type Kind uintconst (Invalid Kind = iota // 非法類型Bool // 布爾型Int // 有符號整型Int8 // 有符號8位整型Int16 // 有符號16位整型Int32 // 有符號32位整型Int64 // 有符號64位整型Uint // 無符號整型Uint8 // 無符號8位整型Uint16 // 無符號16位整型Uint32 // 無符號32位整型Uint64 // 無符號64位整型Uintptr // 指針Float32 // 單精度浮點數Float64 // 雙精度浮點數Complex64 // 64位復數類型Complex128 // 128位復數類型Array // 數組Chan // 通道Func // 函數Interface // 接口Map // 映射Ptr // 指針Slice // 切片String // 字符串Struct // 結構體UnsafePointer // 底層指針
)
2.2 從類型對象中獲取類型名稱和種類
Go 語言中的類型名稱對應的反射獲取方法是 reflect.Type 中的 Name() 方法,返回表示類型名稱的字符串。
類型歸屬的種類( Kind )使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 類型的常量。
package mainimport ("fmt""reflect"
)// 定義一個Enum類型
type Enum intconst (Zero Enum = 0
)func main() {// 聲明一個空結構體type cat struct {}// 將 cat 實例化,并且使用 reflect.TypeOf() 獲取被實例化后的 cat 的反射類型對象typeOfCat := reflect.TypeOf(cat{})// 顯示反射類型對象的名稱和種類fmt.Println(typeOfCat.Name(), typeOfCat.Kind()) // cat struct// 獲取Zero常量的反射類型對象typeOfA := reflect.TypeOf(Zero)// 顯示反射類型對象的名稱和種類fmt.Println(typeOfA.Name(), typeOfA.Kind()) // Enum int}
3. 獲取指針指向的元素類型
Go 語言程序中對指針獲取反射對象時,可以通過 reflect.Elem() 方法獲取這個指針指向的元素類型。這個獲取過程被稱為取元素,等效于對指針類型變量做了一個*操作,代碼如下:
package mainimport ("fmt""reflect"
)func main() {// 聲明一個空結構體type cat struct {}// 創建cat的實例,ins 是一個 *cat 類型的指針變量ins := &cat{}// 獲取結構體指針實例的反射類型對象typeOfCat := reflect.TypeOf(ins)// 輸出指針變量的類型名稱和種類// Go 語言的反射中對所有指針變量的種類都是 Ptr,但注意,指針變量的類型名稱是空,不是 *catfmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())// 取指針類型的元素類型,也就是 cat 類型。這個操作不可逆,不可以通過一個非指針類型獲取它的指針類型typeOfCat = typeOfCat.Elem()// 顯示反射類型對象的名稱和種類fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())}
輸出:
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'
4. 獲取結構體成員類型
任意值通過 reflect.TypeOf() 獲得反射對象信息后,如果它的類型是結構體,可以通過反射值對象(reflect.Type)的 NumField() 和 Field() 方法獲得結構體成員的詳細信息。
和 reflect.Value 不同,reflect.Type 是一個接口,而不是一個結構體,所以也只能使用它的方法。
以下是 reflect.Type 接口常用的方法。從這個列表來看,大部分都和 reflect.Value 的方法功能相同。
type Type interface {Implements(u Type) boolAssignableTo(u Type) boolConvertibleTo(u Type) boolComparable() bool//以下這些方法和Value結構體的功能相同Kind() KindMethod(int) MethodMethodByName(string) (Method, bool)NumMethod() intElem() TypeField(i int) StructFieldFieldByIndex(index []int) StructFieldFieldByName(name string) (StructField, bool)FieldByNameFunc(match func(string) bool) (StructField, bool)NumField() int
}
其中幾個特有的方法如下:
-
Implements方法用于判斷是否實現了接口u; -
AssignableTo方法用于判斷是否可以賦值給類型u,其實就是是否可以使用=,即賦值運算符; -
ConvertibleTo方法用于判斷是否可以轉換成類型u,其實就是是否可以進行類型轉換; -
Comparable方法用于判斷該類型是否是可比較的,其實就是是否可以使用關系運算符進行比較。
與成員獲取相關的 reflect.Type 的方法如下表所示。結構體成員訪問的方法列表:
| 方法 | 說明 |
|---|---|
| Field(i int) StructField | 根據索引,返回索引對應的結構體字段的信息。當值不是結構體或索引超界時發生宕機 |
| NumField() int | 返回結構體成員字段數量。當類型不是結構體或索引超界時發生宕機 |
| FieldByName(name string) (StructField, bool) | 根據給定字符串返回字符串對應的結構體字段的信息。沒有找到時 bool 返回 false,當類型不是結構體或索引超界時發生宕機 |
| FieldByIndex(index []int) StructField | 多層成員訪問時,根據 []int 提供的每個結構體的字段索引,返回字段的信息。沒有找到時返回零值。當類型不是結構體或索引超界時 發生宕機 |
| FieldByNameFunc( match func(string) bool) (StructField,bool) | 根據匹配函數匹配需要的字段。當值不是結構體或索引超界時發生宕機 |
4.1 結構體字段類型
reflect.Type 的 Field() 方法返回 StructField 結構,這個結構描述結構體的成員信息,通過這個信息可以獲取成員與結構體的關系,如偏移、索引、是否為匿名字段、結構體標簽(Struct Tag)等,而且還可以通過 StructField 的 Type 字段進一步獲取結構體成員的類型信息。StructField 的結構如下:
type StructField struct {Name string // 字段名PkgPath string // 字段在結構體中的路徑Type Type // 字段本身的反射類型對象,類型為 reflect.Type,Tag StructTag // 結構體標簽,為結構體字段標簽的額外信息,可以單獨提取Offset uintptr // 字段在結構體中的相對偏移Index []int // Type.FieldByIndex中的返回的索引值Anonymous bool // 是否為匿名字段
}
4.2 遍歷結構體的字段和方法
可以通過 NumField 方法獲取結構體字段的數量,然后使用 for 循環,通過 Field 方法就可以遍歷結構體的字段,并打印出字段名稱。同理,遍歷結構體的方法也是同樣的思路,代碼也類似,如下所示:
package mainimport ("fmt""reflect"
)type person struct {Name stringAge int
}func (p person) String() string {return fmt.Sprintf("Name is %s,Age is %d", p.Name, p.Age)
}func main() {p := person{Name: "wohu", Age: 18}pt := reflect.TypeOf(p)//遍歷person的字段for i := 0; i < pt.NumField(); i++ {fmt.Println("字段:", pt.Field(i).Name)}//遍歷person的方法for i := 0; i < pt.NumMethod(); i++ {fmt.Println("方法:", pt.Method(i).Name)}
}
4.3 獲取成員反射信息
反射訪問結構體成員類型及信息:
package mainimport ("fmt""reflect"
)func main() {// 聲明一個空結構體type cat struct {Name string// 帶有結構體tag的字段Type int `json:"type" id:"100"`// `json:"type" id:"100"` 這個字符串在 Go 語言中被稱為 Tag(標簽)。// 一般用于給字段添加自定義信息,方便其他模塊根據信息進行不同功能的處理。}// 創建cat的實例ins := cat{Name: "mimi", Type: 1}// 獲取結構體實例的反射類型對象typeOfCat := reflect.TypeOf(ins)// 遍歷結構體所有成員for i := 0; i < typeOfCat.NumField(); i++ {// 獲取每個成員的結構體字段類型fieldType := typeOfCat.Field(i) // 使用 reflect.Type 的 Field() 方法返回的結構不再是 reflect.Type 而是StructField 結構體。// 輸出成員名和tagfmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)}// 通過字段名, 找到字段類型信息if catType, ok := typeOfCat.FieldByName("Type"); ok {// 從tag中取出需要的tagfmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))}
}
輸出結果:
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
5. 獲取結構體成員的值
reflect.Value 可以通過函數 reflect.ValueOf 獲得,下面我將為你介紹它的結構和用法。
在 Go 語言中,reflect.Value 被定義為一個 struct 結構體,它的定義如下面的代碼所示:
type Value struct {typ *rtypeptr unsafe.Pointerflag
}
我們發現 reflect.Value 結構體的字段都是私有的,也就是說,我們只能使用 reflect.Value 的方法。現在看看它有哪些常用方法,如下所示:
//針對具體類型的系列方法
//以下是用于獲取對應的值
Bool
Bytes
Complex
Float
Int
String
Uint
CanSet //是否可以修改對應的值
以下是用于修改對應的值
Set
SetBool
SetBytes
SetComplex
SetFloat
SetInt
SetString
Elem //獲取指針指向的值,一般用于修改對應的值
//以下Field系列方法用于獲取struct類型中的字段
Field
FieldByIndex
FieldByName
FieldByNameFunc
Interface //獲取對應的原始類型
IsNil //值是否為nil
IsZero //值是否是零值
Kind //獲取對應的類型類別,比如Array、Slice、Map等
//獲取對應的方法
Method
MethodByName
NumField //獲取struct類型中字段的數量
NumMethod//類型上方法集的數量
Type//獲取對應的reflect.Type
看著比較多,其實就三類:
- 一類用于獲取和修改對應的值;
- 一類和 struct 類型的字段有關,用于獲取對應的字段;
- 一類和類型上的方法集有關,用于獲取對應的方法;
5.1 獲取原始類型
在上面的例子中,我通過 reflect.ValueOf 函數把任意類型的對象轉為一個 reflect.Value,而如果想逆向轉回來也可以,reflect.Value 為我們提供了 Inteface 方法,如下面的代碼所示:
func main() {s := "hello"sv := reflect.ValueOf(s)ss := sv.Interface().(string)fmt.Println(sv, ss) // hello hello
}
5.2 修改對應的值
已經定義的變量可以通過反射在運行時修改,比如上面的示例 s=“hello”,修改為 “world”, 如下所示:
func main() {s := "hello"sv := reflect.ValueOf(&s) // 必須是&s,否則報錯// panic: reflect: call of reflect.Value.Elem on string Valuesv.Elem().SetString("world")fmt.Println(s) // world
}
這樣就通過反射修改了一個變量。因為 reflect.ValueOf 函數返回的是一份值的拷貝,所以我們要傳入變量的指針才可以。 因為傳遞的是一個指針,所以需要調用 Elem 方法找到這個指針指向的值,這樣才能修改。 最后我們就可以使用 SetString 方法修改值了。
要修改一個變量的值,有幾個關鍵點:傳遞指針(可尋址),通過 Elem 方法獲取指向的值,才可以保證值可以被修改,reflect.Value 為我們提供了 CanSet 方法判斷是否可以修改該變量。
那么如何修改 struct 結構體字段的值呢?參考變量的修改方式,可總結出以下步驟:
-
傳遞一個
struct結構體的指針,獲取對應的reflect.Value; -
通過
Elem方法獲取指針指向的值; -
通過
Field方法獲取要修改的字段; -
通過
Set系列方法修改成對應的值。
運行下面的代碼,你會發現變量 p 中的 Name 字段已經被修改為張三了。
func main() {p := person{Name: "wohu", Age: 18}ppv := reflect.ValueOf(&p)ppv.Elem().Field(0).SetString("張三")fmt.Println(p) // {張三 18}
}type person struct {Name stringAge int
}
最后再來總結一下通過反射修改一個值的規則。
-
可被尋址,通俗地講就是要向
reflect.ValueOf函數傳遞一個指針作為參數。 -
如果要修改
struct結構體字段值的話,該字段需要是可導出的,而不是私有的,也就是該字段的首字母為大寫。 -
記得使用
Elem方法獲得指針指向的值,這樣才能調用Set系列方法進行修改。
記住以上規則,你就可以在程序運行時通過反射修改一個變量或字段的值。
5.3 獲取對應的底層類型
底層類型是什么意思呢?其實對應的主要是基礎類型,比如接口、結構體、指針…因為我們可以通過 type 關鍵字聲明很多新的類型。
比如在上面的例子中,變量 p 的實際類型是 person,但是 person 對應的底層類型是 struct 這個結構體類型,而 &p 對應的則是指針類型。我們來通過下面的代碼進行驗證:
type person struct {Name stringAge int
}func main() {p := person{Name: "wohu", Age: 18}ppv := reflect.ValueOf(&p)fmt.Println(ppv.Kind()) // ptrpv := reflect.ValueOf(p)fmt.Println(pv.Kind()) // struct
}
Kind 方法返回一個 Kind 類型的值,它是一個常量,具體可見 2.1 節
反射值對象(reflect.Value)提供對結構體訪問的方法,通過這些方法可以完成對結構體任意值的訪問,如下表所示。
| 方 法 | 備 注 | |
|---|---|---|
| Field(i int) Value | 根據索引,返回索引對應的結構體成員字段的反射值對象。當值不是結構體或索引超界時發生宕機 | |
| NumField() int | 返回結構體成員字段數量。當值不是結構體或索引超界時發生宕機 | |
| FieldByName(name string) Value | 根據給定字符串返回字符串對應的結構體字段。沒有找到時返回零值,當值不是結構體或索引超界時發生宕機 | |
| FieldByIndex(index []int) Value | 多層成員訪問時,根據 []int 提供的每個結構體的字段索引,返回字段的值。 沒有找到時返回零值,當值不是結構體或索引超界時發生宕機 | |
| FieldByNameFunc(match func(string) bool) Value | 根據匹配函數匹配需要的字段。找到時返回零值,當值不是結構體或索引超界時發生宕機 |
下面代碼構造一個結構體包含不同類型的成員。通過 reflect.Value 提供的成員訪問函數,可以獲得結構體值的各種數據。
package mainimport ("fmt""reflect"
)// 定義結構體
type dummy struct {a intb string// 嵌入字段float32boolnext *dummy
}func main() {// 值包裝結構體d := reflect.ValueOf(dummy{next: &dummy{},})// 獲取字段數量fmt.Println("NumField", d.NumField())// 獲取索引為2的字段(float32字段)floatField := d.Field(2)// 輸出字段類型fmt.Println("Field", floatField.Type())// 根據名字查找字段fmt.Println("FieldByName(\"b\").Type", d.FieldByName("b").Type())// 根據索引查找值中, next字段的int字段的值fmt.Println("FieldByIndex([]int{4, 0}).Type()", d.FieldByIndex([]int{4, 0}).Type())
}
6. Go 語言結構體標簽
通過 reflect.Type 獲取結構體成員信息 reflect.StructField 結構中的 Tag 被稱為結構體標簽(Struct Tag)。結構體標簽是對結構體字段的額外信息標簽。
6.1 結構體標簽格式
Tag 在結構體字段后方書寫的格式如下:
`key1:"value1" key2:"value2"`
結構體標簽由一個或多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。鍵值對之間使用一個空格分隔。
6.2 從結構體標簽中獲取值
StructTag 擁有一些方法,可以進行 Tag 信息的解析和提取,如下所示:
func(tag StructTag)Get(key string)string
根據Tag中的鍵獲取對應的值,例如key1:"value1"key2:"value2"的 Tag 中,可以傳入“key1”獲得“value1”。func(tag StructTag)Lookup(key string)(value string,ok bool)
根據 Tag 中的鍵,查詢值是否存在
6.3 結構體標簽格式錯誤導致的問題
編寫 Tag 時,必須嚴格遵守鍵值對的規則。結構體標簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,參見下面這個例子:
package mainimport ("fmt""reflect"
)func main() {type cat struct {Name stringType int `json: "type" id:"100"`}typeOfCat := reflect.TypeOf(cat{})if catType, ok := typeOfCat.FieldByName("Type"); ok {fmt.Println(catType.Tag.Get("json"))}}
代碼輸出空字符串,并不會輸出期望的 type。
第 12 行中,在json:和"type"之間增加了一個空格。這種寫法沒有遵守結構體標簽的規則,因此無法通過 Tag.Get 獲取到正確的 json 對應的值。
7. 反射獲取值信息
當我們將一個接口值傳遞給一個 reflect.ValueOf 函數調用時,此調用返回的是代表著此接口值的動態值的一個 reflect.Value 值。我們必須通過間接的途徑獲得一個代表一個接口值的 reflect.Value 值。
一個 reflect.Value 值的 CanSet 方法將返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被賦值)。如果一個 Go 值可以被修改,則我們可以調用對應的 reflect.Value 值的 Set 方法來修改此 Go 值。
注意:reflect.ValueOf 函數直接返回的 reflect.Value 值都是不可修改的。
反射不僅可以獲取值的類型信息,還可以動態地獲取或者設置變量的值。Go 語言中使用 reflect.Value 獲取和設置變量的值。
7.1 使用反射值對象包裝任意值
Go 語言中,使用 reflect.ValueOf() 函數獲得值的反射值對象(reflect.Value)。書寫格式如下:
value := reflect.ValueOf(rawValue)
reflect.ValueOf 返回 reflect.Value 類型,包含有 rawValue 的值信息。 reflect.Value 與原值間可以通過值包裝和值獲取互相轉化。 reflect.Value 是一些反射操作的重要類型,如反射調用函數。
7.2 從反射值對象獲取被包裝的值
可以通過下面幾種方法從反射值對象 reflect.Value 中獲取原值,如下表所示。
| 方法名 | 說 明 |
|---|---|
| Interface() interface {} | 將值以 interface{} 類型返回,可以通過類型斷言轉換為指定類型 |
| Int() int64 | 將值以 int 類型返回,所有有符號整型均可以此方式返回 |
| Uint() uint64 | 將值以 uint 類型返回,所有無符號整型均可以此方式返回 |
| Float() float64 | 將值以雙精度(float64)類型返回,所有浮點數(float32、float64)均可以此方式返回 |
| Bool() bool | 將值以 bool 類型返回 |
| Bytes() []bytes | 將值以字節數組 []bytes 類型返回 |
| String() string | 將值以字符串類型返回 |
下面代碼中,將整型變量中的值使用 reflect.Value 獲取反射值對象(reflect.Value)。再通過 reflect.Value 的 Interface() 方法獲得 interface{} 類型的原值,通過 int 類型對應的 reflect.Value 的 Int() 方法獲得整型值。
package mainimport ("fmt""reflect"
)func main() {// 聲明整型變量a并賦初值var a int = 1024// 獲取變量a的反射值對象valueOfA := reflect.ValueOf(a)// 獲取interface{}類型的值, 通過類型斷言轉換var getA int = valueOfA.Interface().(int)// 獲取64位的值, 強制類型轉換為int類型var getA2 int = int(valueOfA.Int())fmt.Println(getA, getA2) // 1024 1024
}
8. 判斷反射值的空和有效性
反射值對象( reflect.Value )提供一系列方法進行零值和空判定,如下表所示。
| 方 法 | 說 明 |
|---|---|
| IsNil() bool | 返回值是否為 nil。如果值類型不是通道(channel)、函數、接口、map、指針或 切片時發生 panic,類似于語言層的v== nil操作 |
| IsValid() bool | 判斷值是否有效。 當值本身非法時,返回 false,例如 reflect Value不包含任何值,值為 nil 等。 |
下面的例子將會對各種方式的空指針進行 IsNil() 和 IsValid() 的返回值判定檢測。同時對結構體成員及方法查找 map 鍵值對的返回值進行 IsValid() 判定,參考下面的代碼。
package mainimport ("fmt""reflect"
)func main() {// *int的空指針var a *intfmt.Println("var a *int:", reflect.ValueOf(a).IsNil())// nil值fmt.Println("nil:", reflect.ValueOf(nil).IsValid())// *int類型的空指針fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())// 實例化一個結構體s := struct{}{}// 嘗試從結構體中查找一個不存在的字段fmt.Println("不存在的結構體成員:", reflect.ValueOf(s).FieldByName("").IsValid())// 嘗試從結構體中查找一個不存在的方法fmt.Println("不存在的結構體方法:", reflect.ValueOf(s).MethodByName("").IsValid())// 實例化一個mapm := map[int]int{}// 嘗試從map中查找一個不存在的鍵fmt.Println("不存在的鍵:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
9. 通過反射修改變量的值
10. 通過類型信息創建實例
11. 通過反射調用函數
12. 反射定律
反射是計算機語言中程序檢視其自身結構的一種方法,它屬于元編程的一種形式。反射靈活、強大,但也存在不安全。它可以繞過編譯器的很多靜態檢查,如果過多使用便會造成混亂。為了幫助開發者更好地理解反射,Go 語言的作者在博客上總結了反射的三大定律。
-
任何接口值 interface{} 都可以反射出反射對象,也就是 reflect.Value 和 reflect.Type,通過函數 reflect.ValueOf 和 reflect.TypeOf 獲得。
-
反射對象也可以還原為 interface{} 變量,也就是第 1 條定律的可逆性,通過 reflect.Value 結構體的 Interface 方法獲得。
-
要修改反射的對象,該值必須可設置,也就是可尋址,參考上節課修改變量的值那一節的內容理解。
小提示:任何類型的變量都可以轉換為空接口 intferface{},所以第 1 條定律中函數 reflect.ValueOf 和 reflect.TypeOf 的參數就是 interface{},表示可以把任何類型的變量轉換為反射對象。在第 2 條定律中,reflect.Value 結構體的 Interface 方法返回的值也是 interface{},表示可以把反射對象還原為對應的類型變量。
一旦你理解了這三大定律,就可以更好地理解和使用 Go 語言反射。
總結
在反射中,reflect.Value 對應的是變量的值,如果你需要進行和變量的值有關的操作,應該優先使用 reflect.Value,比如獲取變量的值、修改變量的值等。reflect.Type 對應的是變量的類型,如果你需要進行和變量的類型本身有關的操作,應該優先使用 reflect.Type,比如獲取結構體內的字段、類型擁有的方法集等。
此外我要再次強調:反射雖然很強大,可以簡化編程、減少重復代碼,但是過度使用會讓你的代碼變得復雜混亂。所以除非非常必要,否則盡可能少地使用它們。
總結
以上是生活随笔為你收集整理的Go 学习笔记(39)— Go 反射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go 学习笔记(37)— 标准命令(go
- 下一篇: 富贵花开是谁画的呢?