目錄
反射是指在程序運(yùn)行期對(duì)程序本身進(jìn)行訪問和修改的能力唁桩。程序在編譯時(shí)瘫想,變量被轉(zhuǎn)換為內(nèi)存地址,變量名不會(huì)被編譯器寫入到可執(zhí)行部分瓮钥。在運(yùn)行程序時(shí)探遵,程序無法獲取自身的信息。
支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱囱晴、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中瓢谢,并給程序提供接口訪問反射信息畸写,這樣就可以在程序運(yùn)行期獲取類型的反射信息,并且有能力修改它們氓扛。
Go程序在運(yùn)行期使用reflect包訪問程序的反射信息枯芬。
reflect包實(shí)現(xiàn)了運(yùn)行時(shí)反射,允許程序操作任意類型的對(duì)象幢尚。典型用法是用靜態(tài)類型interface{}保存一個(gè)值破停,通過調(diào)用TypeOf獲取其動(dòng)態(tài)類型信息,該函數(shù)返回一個(gè)Type類型值尉剩。調(diào)用ValueOf函數(shù)返回一個(gè)Value類型值真慢,該值代表運(yùn)行時(shí)的數(shù)據(jù)。Zero接受一個(gè)Type類型參數(shù)并返回一個(gè)代表該類型零值的Value類型值理茎。
Go 程序的反射系統(tǒng)無法獲取到一個(gè)可執(zhí)行文件空間中或者是一個(gè)包中的所有類型信息黑界,需要配合使用標(biāo)準(zhǔn)庫中對(duì)應(yīng)的詞法、語法解析器和抽象語法樹(AST)對(duì)源碼進(jìn)行掃描后獲得這些信息皂林。
通過反射獲取類型信息
先上圖朗鸠,有了這張圖再看代碼的時(shí)候就會(huì)感覺很好理解了。
通過反射獲取類型信息:(reflect.TypeOf()和reflect.Type)
使用 reflect.TypeOf() 函數(shù)可以獲得任意值的類型對(duì)象(reflect.Type)础倍,程序通過類型對(duì)象可以訪問任意值的類型信息烛占。下面通過例子來理解獲取類型對(duì)象的過程:
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
var stu Student
typeOfStu := reflect.TypeOf(stu)
fmt.Println(typeOfStu.Name(), typeOfStu.Kind())
}
代碼輸出如下:
Student struct
代碼說明如下:
- 第16行,定義一個(gè)int類型的變量
- 第18行,通過reflect.TypeOf()取得變量stu的類型對(duì)象typeOfStu,類型為reflect.Type
- 第20行中忆家,通過typeOfStu類型對(duì)象的成員函數(shù)犹菇,可以分別獲取到 typeOfStu 變量的類型名為 Student,種類(Kind)為 struct芽卿。
理解反射的類型(Type)與種類(Kind)
在使用反射時(shí)揭芍,需要首先理解類型(Type)和種類(Kind)的區(qū)別。編程中卸例,使用最多的是類型称杨,但在反射中,當(dāng)需要區(qū)分一個(gè)大品種的類型時(shí)筷转,就會(huì)用到種類(Kind)姑原。例如,需要統(tǒng)一判斷類型中的指針時(shí)旦装,使用種類(Kind)信息就較為方便页衙。
反射種類(Kind)的定義
Go 程序中的類型(Type)指的是系統(tǒng)原生數(shù)據(jù)類型,如 int阴绢、string店乐、bool、float32 等類型呻袭,以及使用 type 關(guān)鍵字定義的類型眨八,這些類型的名稱就是其類型本身的名稱。例如使用 type A struct{} 定義結(jié)構(gòu)體時(shí)左电,A 就是 struct{} 的類型廉侧。
種類(Kind)指的是對(duì)象歸屬的品種,在 reflect 包中有如下定義:
type Kind uint
const (
Invalid Kind = iota // 非法類型
Bool // 布爾型
Int // 有符號(hào)整型
Int8 // 有符號(hào)8位整型
Int16 // 有符號(hào)16位整型
Int32 // 有符號(hào)32位整型
Int64 // 有符號(hào)64位整型
Uint // 無符號(hào)整型
Uint8 // 無符號(hào)8位整型
Uint16 // 無符號(hào)16位整型
Uint32 // 無符號(hào)32位整型
Uint64 // 無符號(hào)64位整型
Uintptr // 指針
Float32 // 單精度浮點(diǎn)數(shù)
Float64 // 雙精度浮點(diǎn)數(shù)
Complex64 // 32位復(fù)數(shù)類型
Complex128 // 64位復(fù)數(shù)類型
Array // 數(shù)組
Chan // 通道
Func // 函數(shù)
Interface // 接口
Map // 映射
Ptr // 指針
Slice // 切片
String // 字符串
Struct // 結(jié)構(gòu)體
UnsafePointer // 底層指針
)
Map篓足、Slice段誊、Chan 屬于引用類型,使用起來類似于指針栈拖,但是在種類常量定義中仍然屬于獨(dú)立的種類连舍,不屬于 Ptr。
type A struct{} 定義的結(jié)構(gòu)體屬于 Struct 種類涩哟,*A 屬于 Ptr索赏。
從類型對(duì)象中獲取類型名稱和種類的例子
Go 語言中的類型名稱對(duì)應(yīng)的反射獲取方法是 reflect.Type 中的 Name() 方法,返回表示類型名稱的字符串贴彼。
類型歸屬的種類(Kind)使用的是 reflect.Type 中的 Kind() 方法潜腻,返回 reflect.Kind 類型的常量。
下面的代碼中會(huì)對(duì)常量和結(jié)構(gòu)體進(jìn)行類型信息獲取器仗。
package main
import (
"fmt"
"reflect"
)
//定義一個(gè)Enum類型
type Enum int
const (
Zero Enum = 0
)
type Student struct {
Name string
Age int
}
func main() {
//定義一個(gè)Student類型的變量
var stu Student
//獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfStu := reflect.TypeOf(stu)
//顯示反射類型對(duì)象的名稱和種類
fmt.Println(typeOfStu.Name(), typeOfStu.Kind())
//獲取Zero常量的反射類型對(duì)象
typeOfZero := reflect.TypeOf(Zero)
//顯示反射類型對(duì)象的名稱和種類
fmt.Println(typeOfZero.Name(), typeOfZero.Kind())
}
代碼輸出如下:
Student struct
Enum int
代碼說明如下:
- 第21行融涣,將 Student 實(shí)例化,并且使用 reflect.TypeOf() 獲取被實(shí)例化后的 Student 的反射類型對(duì)象。
- 第27行暴心,輸出Student的類型名稱和種類妓盲,類型名稱就是 Student,而 Student 屬于一種結(jié)構(gòu)體種類专普,因此種類為 struct。
- 第30行弹沽,Zero 是一個(gè) Enum 類型的常量檀夹。這個(gè) Enum 類型在第 9 行聲明,第 12 行聲明了常量策橘。如沒有常量也不能創(chuàng)建實(shí)例炸渡,通過 reflect.TypeOf() 直接獲取反射類型對(duì)象。
- 第33行丽已,輸出 Zero 對(duì)應(yīng)的類型對(duì)象的類型名和種類蚌堵。
reflect.Elem() - 通過反射獲取指針指向的元素類型
通過反射獲取指針指向的元素類型:reflect.Elem()
Go 程序中對(duì)指針獲取反射對(duì)象時(shí),可以通過 reflect.Elem() 方法獲取這個(gè)指針指向的元素類型沛婴。這個(gè)獲取過程被稱為取元素吼畏,等效于對(duì)指針類型變量做了一個(gè)*
操作,代碼如下:
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
//定義一個(gè)Student類型的指針變量
var stu = &Student{Name:"kitty", Age: 20}
//獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfStu := reflect.TypeOf(stu)
//顯示反射類型對(duì)象的名稱和種類
fmt.Printf("name: '%v', kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
//取類型的元素
typeOfStu = typeOfStu.Elem()
//顯示反射類型對(duì)象的名稱和種類
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
}
代碼輸出如下:
name: '', kind: 'ptr'
element name: 'Student', element kind: 'struct'
代碼說明如下:
- 第17行嘁灯,創(chuàng)建了一個(gè)Student結(jié)構(gòu)體的實(shí)例泻蚊,stu是一個(gè)*Student的指針變量
- 第20行,對(duì)指針變量獲取反射類型信息丑婿。
- 第23行性雄,輸出指針變量的類型名稱和種類。Go語言的反射中對(duì)所有指針變量的種類都是 Ptr羹奉,但注意秒旋,指針變量的類型名稱是空,不是 *Student诀拭。
- 第26行迁筛,取指針類型的元素類型,也就是 Student 類型炫加。這個(gè)操作不可逆瑰煎,不可以通過一個(gè)非指針類型獲取它的指針類型。
- 第29行俗孝,輸出指針變量指向元素的類型名稱和種類酒甸,得到了 Student 的類型名稱(Student)和種類(struct)。
通過反射獲取結(jié)構(gòu)體的成員類型
任意值通過 reflect.TypeOf() 獲得反射對(duì)象信息后赋铝,如果它的類型是結(jié)構(gòu)體插勤,可以通過反射值對(duì)象(reflect.Type)的 NumField() 和 Field() 方法獲得結(jié)構(gòu)體成員的詳細(xì)信息。與成員獲取相關(guān)的 reflect.Type 的方法如下表所示。
<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">結(jié)構(gòu)體成員訪問的方法列表</center>
方法 | 說明 |
---|---|
Field(i int) StructField | 根據(jù)索引农尖,返回索引對(duì)應(yīng)的結(jié)構(gòu)體字段的信息析恋。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
NumField() int | 返回結(jié)構(gòu)體成員字段數(shù)量。當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
FieldByName(name string) (StructField, bool) | 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段的信息盛卡。沒有找到時(shí) bool 返回 false助隧,當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
FieldByIndex(index []int) StructField | 多層成員訪問時(shí),根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引滑沧,返回字段的信息并村。沒有找到時(shí)返回零值。當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí) 發(fā)生宕機(jī) |
FieldByNameFunc( match func(string) bool) (StructField,bool) | 根據(jù)匹配函數(shù)匹配需要的字段滓技。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
結(jié)構(gòu)體字段類型
Type 的 Field() 方法返回 StructField 結(jié)構(gòu)哩牍,這個(gè)結(jié)構(gòu)描述結(jié)構(gòu)體的成員信息,通過這個(gè)信息可以獲取成員與結(jié)構(gòu)體的關(guān)系令漂,如偏移膝昆、索引、是否為匿名字段叠必、結(jié)構(gòu)體標(biāo)簽(Struct Tag)等荚孵,而且還可以通過 StructField 的 Type 字段進(jìn)一步獲取結(jié)構(gòu)體成員的類型信息。StructField 的結(jié)構(gòu)如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路徑
Type Type // 字段反射類型對(duì)象
Tag StructTag // 字段的結(jié)構(gòu)體標(biāo)簽
Offset uintptr // 字段在結(jié)構(gòu)體中的相對(duì)偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否為匿名字段
}
字段說明如下挠唆。
- Name:為字段名稱处窥。
- PkgPath:字段在結(jié)構(gòu)體中的路徑。
- Type:字段本身的反射類型對(duì)象玄组,類型為 reflect.Type滔驾,可以進(jìn)一步獲取字段的類型信息。
- Tag:結(jié)構(gòu)體標(biāo)簽俄讹,為結(jié)構(gòu)體字段標(biāo)簽的額外信息哆致,可以單獨(dú)提取。
- Index:FieldByIndex 中的索引順序患膛。
- Anonymous:表示該字段是否為匿名字段摊阀。
獲取成員反射信息
下面代碼中,實(shí)例化一個(gè)結(jié)構(gòu)體并遍歷其結(jié)構(gòu)體成員踪蹬,再通過 reflect.Type 的 FieldByName() 方法查找結(jié)構(gòu)體中指定名稱的字段胞此,直接獲取其類型信息。
反射訪問結(jié)構(gòu)體成員類型及信息:
package main
import (
"fmt"
"reflect"
)
func main() {
// 聲明一個(gè)空結(jié)構(gòu)體
type cat struct {
Name string
// 帶有結(jié)構(gòu)體tag的字段
Type int `json:"type" id:"100"`
}
// 創(chuàng)建cat的實(shí)例
ins := cat{Name: "mimi", Type: 1}
// 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfCat := reflect.TypeOf(ins)
// 遍歷結(jié)構(gòu)體所有成員
for i := 0; i < typeOfCat.NumField(); i++ {
// 獲取每個(gè)成員的結(jié)構(gòu)體字段類型
fieldType := typeOfCat.Field(i)
// 輸出成員名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通過字段名, 找到字段類型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 從tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
代碼輸出如下:
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
代碼說明如下:
- 第 11 行跃捣,聲明了帶有兩個(gè)成員的 cat 結(jié)構(gòu)體漱牵。
- 第 15 行,Type 是 cat 的一個(gè)成員疚漆,這個(gè)成員類型后面帶有一個(gè)以```開始和結(jié)尾的字符串酣胀。這個(gè)字符串在 Go語言中被稱為 Tag(標(biāo)簽)刁赦。一般用于給字段添加自定義信息,方便其他模塊根據(jù)信息進(jìn)行不同功能的處理闻镶。
- 第 19 行甚脉,創(chuàng)建 cat 實(shí)例,并對(duì)兩個(gè)字段賦值铆农。結(jié)構(gòu)體標(biāo)簽屬于類型信息牺氨,無須且不能賦值。
- 第 22 行墩剖,獲取實(shí)例的反射類型對(duì)象波闹。
- 第 25 行,使用 reflect.Type 類型的 NumField() 方法獲得一個(gè)結(jié)構(gòu)體類型共有多少個(gè)字段涛碑。如果類型不是結(jié)構(gòu)體,將會(huì)觸發(fā)宕機(jī)錯(cuò)誤孵淘。
- 第 28 行蒲障,reflect.Type 中的 Field() 方法和 NumField 一般都是配對(duì)使用,用來實(shí)現(xiàn)結(jié)構(gòu)體成員的遍歷操作瘫证。
- 第 31 行揉阎,使用 reflect.Type 的 Field() 方法返回的結(jié)構(gòu)不再是 reflect.Type 而是StructField 結(jié)構(gòu)體。
- 第 35 行背捌,使用 reflect.Type 的 FieldByName() 根據(jù)字段名查找結(jié)構(gòu)體字段信息毙籽,cat Type 表示返回的結(jié)構(gòu)體字段信息,類型為 StructField毡庆,ok 表示是否找到結(jié)構(gòu)體字段的信息坑赡。
- 第 38 行中,使用 StructField 中 Tag 的 Get() 方法么抗,根據(jù) Tag 中的名字進(jìn)行信息獲取毅否。
通過反射獲取值信息
反射不僅可以獲取值的類型信息,還可以動(dòng)態(tài)地獲取或者設(shè)置變量的值蝇刀。Go語言中使用 reflect.Value 獲取和設(shè)置變量的值螟加。
變量、interface{}和reflect.Value是可以相互轉(zhuǎn)換的吞琐。這點(diǎn)在實(shí)際開發(fā)中捆探,會(huì)經(jīng)常碰到。
使用反射值對(duì)象包裝任意值
Go 語言中站粟,使用 reflect.ValueOf() 函數(shù)獲得值的反射值對(duì)象(reflect.Value)黍图。書寫格式如下:
rValue := reflect.ValueOf(rawValue)
reflect.ValueOf 返回 reflect.Value 類型,包含有 rawValue 的值信息卒蘸。reflect.Value 與原值間可以通過值包裝和值獲取互相轉(zhuǎn)化雌隅。reflect.Value 是一些反射操作的重要類型翻默,如反射調(diào)用函數(shù)。
從反射值對(duì)象獲取被包裝的值
Go 語言中可以通過 reflect.Value 重新獲得原始值恰起。
從反射值對(duì)象(reflect.Value)中獲取值得方法
可以通過下面幾種方法從反射值對(duì)象 reflect.Value 中獲取原值修械,如下表所示蚊逢。
<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值獲取原始值的方法</center>
方法名 | 說 明 |
---|---|
Interface() interface{} | 將值以 interface{} 類型返回签则,可以通過類型斷言轉(zhuǎn)換為指定類型 |
Int() int64 | 將值以 int 類型返回,所有有符號(hào)整型均可以此方式返回 |
Uint() uint64 | 將值以 uint 類型返回彩届,所有無符號(hào)整型均可以此方式返回 |
Float() float64 | 將值以雙精度(float64)類型返回吨枉,所有浮點(diǎn)數(shù)(float32蹦渣、float64)均可以此方式返回 |
Bool() bool | 將值以 bool 類型返回 |
Bytes() []bytes | 將值以字節(jié)數(shù)組 []bytes 類型返回 |
String() string | 將值以字符串類型返回 |
從反射值對(duì)象(reflect.Value)中獲取值得例子
下面代碼中,將整型變量中的值使用 reflect.Value 獲取反射值對(duì)象(reflect.Value)貌亭。再通過 reflect.Value 的 Interface() 方法獲得 interface{} 類型的原值柬唯,通過 int 類型對(duì)應(yīng)的 reflect.Value 的 Int() 方法獲得整型值。
package main
import (
"fmt"
"reflect"
)
func main() {
//聲明整型變量a并賦初值
var a int = 1024
//獲取變量a的反射值對(duì)象
valueOfA := reflect.ValueOf(a)
//獲取interface{}類型的值圃庭,通過類型斷言轉(zhuǎn)換
var getA int = valueOfA.Interface().(int)
//獲取64位的值锄奢,強(qiáng)制類型轉(zhuǎn)換為int類型
var getB int = int(valueOfA.Int())
fmt.Println(getA, getB)
}
代碼輸出如下:
1024 1024
代碼說明如下:
- 第 11 行,聲明一個(gè)變量剧腻,類型為 int拘央,設(shè)置初值為 1024。
- 第 14 行书在,獲取變量 a 的反射值對(duì)象灰伟,類型為 reflect.Value,這個(gè)過程和 reflect.TypeOf() 類似儒旬。
- 第 17 行栏账,將 valueOfA 反射值對(duì)象以 interface{} 類型取出,通過類型斷言轉(zhuǎn)換為 int 類型并賦值給 getA义矛。
- 第 20 行发笔,將 valueOfA 反射值對(duì)象通過 Int 方法,以 int64 類型取出凉翻,通過強(qiáng)制類型轉(zhuǎn)換了讨,轉(zhuǎn)換為原本的 int 類型。
通過反射訪問結(jié)構(gòu)體成員的值
反射值對(duì)象(reflect.Value)提供對(duì)結(jié)構(gòu)體訪問的方法制轰,通過這些方法可以完成對(duì)結(jié)構(gòu)體任意值的訪問前计,如下表所示。
方 法 | 備 注 |
---|---|
Field(i int) Value | 根據(jù)索引垃杖,返回索引對(duì)應(yīng)的結(jié)構(gòu)體成員字段的反射值對(duì)象男杈。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
NumField() int | 返回結(jié)構(gòu)體成員字段數(shù)量。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
FieldByName(name string) Value | 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段调俘。沒有找到時(shí)返回零值伶棒,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
FieldByIndex(index []int) Value | 多層成員訪問時(shí)旺垒,根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引,返回字段的值肤无。 沒有找到時(shí)返回零值先蒋,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
FieldByNameFunc(match func(string) bool) Value | 根據(jù)匹配函數(shù)匹配需要的字段。找到時(shí)返回零值宛渐,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
下面代碼構(gòu)造一個(gè)結(jié)構(gòu)體包含不同類型的成員竞漾。通過 reflect.Value 提供的成員訪問函數(shù),可以獲得結(jié)構(gòu)體值的各種數(shù)據(jù)窥翩。
反射訪問結(jié)構(gòu)體成員的值:
package main
import (
"fmt"
"reflect"
)
//定義結(jié)構(gòu)體
type Student struct {
Name string
Age int
//嵌入字段
float32
bool
next *Student
}
func main() {
//值包裝結(jié)構(gòu)體
rValue := reflect.ValueOf(Student{
next: &Student{},
})
//獲取字段數(shù)量
fmt.Println("NumField:", rValue.NumField())
//獲取索引為2的字段(float32字段)
//注:經(jīng)過測試發(fā)現(xiàn)Field(i)的參數(shù)索引是從0開始的业岁,
//并且是按照定義的結(jié)構(gòu)體的順序來的,而不是按照字段名字的ASCii碼值來的
floatField := rValue.Field(2)
//輸出字段類型
fmt.Println("Field:", floatField.Type())
//根據(jù)名字查找字段
fmt.Println("FieldByName(\"Age\").Type:", rValue.FieldByName("Age").Type())
//根據(jù)索引查找值中next字段的int字段的值
fmt.Println("FieldByIndex([]int{4, 0}).Type()", rValue.FieldByIndex([]int{4, 0}).Type())
}
輸出結(jié)果為:
NumField: 5
Field: float32
FieldByName("Age").Type: int
FieldByIndex([]int{4, 0}).Type() string
代碼說明如下:
- 第 9 行寇蚊,定義結(jié)構(gòu)體笔时,結(jié)構(gòu)體的每個(gè)字段的類型都不一樣。
- 第 24 行仗岸,實(shí)例化結(jié)構(gòu)體并包裝為 reflect.Value 類型糊闽,成員中包含一個(gè) *Student 的實(shí)例。
- 第 29 行爹梁,獲取結(jié)構(gòu)體的字段數(shù)量。
- 第 34 和 37 行提澎,獲取索引為2的字段值(float32 字段)姚垃,并且打印類型。
- 第 39 行盼忌,根據(jù)
Age
字符串积糯,查找到 Age 字段的類型。 - 第 41 行谦纱,[]int{4,0} 中的 4 表示看成,在 Student 結(jié)構(gòu)中索引值為 4 的成員,也就是 next跨嘉。next 的類型為 Student川慌,也是一個(gè)結(jié)構(gòu)體,因此使用 []int{4,0} 中的 0 繼續(xù)在 next 值的基礎(chǔ)上索引祠乃,結(jié)構(gòu)為 Student 中索引值為 0 的 Name 字段梦重,類型為 string。
判斷反射值得空和有效性
IsNil()和IsValid() -- 判斷反射值的空和有效性
反射值對(duì)象(reflect.Value)提供一系列方法進(jìn)行零值和空判定亮瓷,如下表所示琴拧。
<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象的零值和有效性判斷方法</center>
方 法 | 說 明 |
---|---|
IsNil() bool | 返回值是否為 nil。如果值類型不是通道(channel)嘱支、函數(shù)蚓胸、接口挣饥、map、指針或 切片時(shí)發(fā)生 panic沛膳,類似于語言層的v== nil 操作 |
IsValid() bool | 判斷值是否有效扔枫。 當(dāng)值本身非法時(shí),返回 false于置,例如 reflect Value不包含任何值茧吊,值為 nil 等。 |
下面的例子將會(huì)對(duì)各種方式的空指針進(jìn)行 IsNil() 和 IsValid() 的返回值判定檢測八毯。同時(shí)對(duì)結(jié)構(gòu)體成員及方法查找 map 鍵值對(duì)的返回值進(jìn)行 IsValid() 判定搓侄,參考下面的代碼。
反射值對(duì)象的零值和有效性判斷:
package main
import (
"fmt"
"reflect"
)
func main() {
//*int的空指針
var a *int
fmt.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())
//實(shí)例化一個(gè)結(jié)構(gòu)體
s := struct {}{}
//嘗試從結(jié)構(gòu)體中查找一個(gè)不存在的字段
fmt.Println("不存在的結(jié)構(gòu)體成員:", reflect.ValueOf(s).FieldByName("").IsValid())
//嘗試從結(jié)構(gòu)體中查找一個(gè)不存在的方法
fmt.Println("不存在的方法:", reflect.ValueOf(s).MethodByName("").IsValid())
//實(shí)例化一個(gè)map
m := map[int]int{}
//嘗試從map中查找一個(gè)不存在的鍵
fmt.Println("不存在的鍵:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
輸出結(jié)果:
var a *int: true
nil: false
(*int)(nil): false
不存在的結(jié)構(gòu)體成員: false
不存在的方法: false
不存在的鍵: false
代碼說明如下:
- 第 11 行话速,聲明一個(gè) *int 類型的指針讶踪,初始值為 nil。
- 第 12 行泊交,將變量 a 包裝為 reflect.Value 并且判斷是否為空乳讥,此時(shí)變量 a 為空指針,因此返回 true廓俭。
- 第 15 行云石,對(duì) nil 進(jìn)行 IsValid() 判定(有效性判定),返回 false研乒。
- 第 18 行汹忠,(*int)(nil) 的含義是將 nil 轉(zhuǎn)換為 int,也就是int 類型的空指針雹熬。此行將 nil 轉(zhuǎn)換為 int 類型宽菜,并取指針指向元素。由于 nil 不指向任何元素竿报,int 類型的 nil 也不能指向任何元素铅乡,值不是有效的。因此這個(gè)反射值使用 Isvalid() 判斷時(shí)返回 false烈菌。
- 第 21 行阵幸,實(shí)例化一個(gè)結(jié)構(gòu)體。
- 第 24 行芽世,通過 FieldByName 查找 s 結(jié)構(gòu)體中一個(gè)空字符串的成員侨嘀,如成員不存在,IsValid() 返回 false捂襟。
- 第 27 行咬腕,通過 MethodByName 查找 s 結(jié)構(gòu)體中一個(gè)空字符串的方法,如方法不存在葬荷,IsValid() 返回 false涨共。
- 第 30 行纽帖,實(shí)例化一個(gè) map,這種寫法與 make 方式創(chuàng)建的 map 等效举反。
- 第 33 行懊直,MapIndex() 方法能根據(jù)給定的 reflect.Value 類型的值查找 map,并且返回查找到的結(jié)果火鼻。
IsNil() 常被用于判斷指針是否為空室囊;IsValid() 常被用于判定返回值是否有效。
通過反射修改變量的值
使用 reflect.Value 對(duì)包裝的值進(jìn)行修改時(shí)魁索,需要遵循一些規(guī)則融撞。如果沒有按照規(guī)則進(jìn)行代碼設(shè)計(jì)和編寫,輕則無法修改對(duì)象值粗蔚,重則程序在運(yùn)行時(shí)會(huì)發(fā)生宕機(jī)尝偎。
判斷及獲取元素的相關(guān)方法
使用 reflect.Value 取元素、取地址及修改值的屬性方法請(qǐng)參考下表鹏控。
<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象的判定及獲取元素的方法</center>
方法名 | 備 注 |
---|---|
Elem() Value | 取值指向的元素值致扯,類似于語言層* 操作。當(dāng)值類型不是指針或接口時(shí)發(fā)生宕 機(jī)当辐,空指針時(shí)返回 nil 的 Value |
Addr() Value | 對(duì)可尋址的值返回其地址抖僵,類似于語言層& 操作。當(dāng)值不可尋址時(shí)發(fā)生宕機(jī) |
CanAddr() bool | 表示值是否可尋址 |
CanSet() bool | 返回值能否被修改缘揪。要求值可尋址且是導(dǎo)出的字段 |
值修改相關(guān)方法
使用 reflect.Value 修改值的相關(guān)方法如下表所示裆针。
<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象修改值的方法</center>
Set(x Value) | 將值設(shè)置為傳入的反射值對(duì)象的值 |
---|---|
Setlnt(x int64) | 使用 int64 設(shè)置值。當(dāng)值的類型不是 int寺晌、int8、int16澡刹、 int32呻征、int64 時(shí)會(huì)發(fā)生宕機(jī) |
SetUint(x uint64) | 使用 uint64 設(shè)置值。當(dāng)值的類型不是 uint罢浇、uint8陆赋、uint16、uint32嚷闭、uint64 時(shí)會(huì)發(fā)生宕機(jī) |
SetFloat(x float64) | 使用 float64 設(shè)置值攒岛。當(dāng)值的類型不是 float32、float64 時(shí)會(huì)發(fā)生宕機(jī) |
SetBool(x bool) | 使用 bool 設(shè)置值胞锰。當(dāng)值的類型不是 bod 時(shí)會(huì)發(fā)生宕機(jī) |
SetBytes(x []byte) | 設(shè)置字節(jié)數(shù)組 []bytes值灾锯。當(dāng)值的類型不是 []byte 時(shí)會(huì)發(fā)生宕機(jī) |
SetString(x string) | 設(shè)置字符串值。當(dāng)值的類型不是 string 時(shí)會(huì)發(fā)生宕機(jī) |
以上方法嗅榕,在 reflect.Value 的 CanSet 返回 false 仍然修改值時(shí)會(huì)發(fā)生宕機(jī)顺饮。
在已知值的類型時(shí)吵聪,應(yīng)盡量使用值對(duì)應(yīng)類型的反射設(shè)置值。
值可修改條件之一:可被尋址
通過反射修改變量值的前提條件之一:這個(gè)值必須可以被尋址兼雄。簡單地說就是這個(gè)變量必須能被修改吟逝。示例代碼如下:
package main
import "reflect"
func main() {
//聲明整形變量a并賦初值
var a int = 1024
//獲取變量a的反射值對(duì)象
rValue := reflect.ValueOf(a)
//嘗試將a修改為1(此處會(huì)崩潰)
rValue.SetInt(1)
}
程序運(yùn)行崩潰,打印錯(cuò)誤
panic: reflect: reflect.Value.SetInt using unaddressable value
報(bào)錯(cuò)意思是:SetInt正在使用一個(gè)不能被尋址的值赦肋。從 reflect.ValueOf 傳入的是 a 的值块攒,而不是 a 的地址,這個(gè) reflect.Value 當(dāng)然是不能被尋址的佃乘。將代碼修改一下囱井,重新運(yùn)行:
package main
import (
"fmt"
"reflect"
)
func main() {
//聲明整形變量a并賦初值
var a int = 1024
//獲取變量a的反射值對(duì)象
rValue := reflect.ValueOf(&a)
//取出a地址的元素(a的值)
rValue = rValue.Elem()
//嘗試將a修改為1
rValue.SetInt(1)
//打印a的值
fmt.Println(rValue.Int())
}
代碼輸出
1
下面是對(duì)代碼的分析:
- 第 14 行中,將變量 a 取值后傳給 reflect.ValueOf()恕稠。此時(shí) reflect.ValueOf() 返回的 valueOfA 持有變量 a 的地址琅绅。
- 第 17 行中,使用 reflect.Value 類型的 Elem() 方法獲取 a 地址的元素鹅巍,也就是 a 的值千扶。reflect.Value 的 Elem() 方法返回的值類型也是 reflect.Value。
- 第 20 行骆捧,此時(shí) rValue 表示的是 a 的值且可以尋址澎羞。使用 SetInt() 方法設(shè)置值時(shí)不再發(fā)生崩潰。
- 第 23 行敛苇,正確打印修改的值妆绞。
提示
當(dāng) reflect.Value 不可尋址時(shí),使用 Addr()
方法也是無法取到值的地址的枫攀,同時(shí)會(huì)發(fā)生宕機(jī)括饶。雖然說 reflect.Value 的 Addr() 方法類似于語言層的&
操作;Elem()
方法類似于語言層的*
操作来涨,但并不代表這些方法與語言層操作等效图焰。
值可修改條件之一:被導(dǎo)出
結(jié)構(gòu)體成員中,如果字段沒有被導(dǎo)出蹦掐,即便不使用反射也可以被訪問技羔,但不能通過反射修改,代碼如下:
package main
import "reflect"
func main() {
type dog struct {
legCount int
}
//獲取dog實(shí)例的反射值對(duì)象
valueOfDog := reflect.ValueOf(&dog{})
valueOfDog = valueOfDog.Elem()
//獲取legCount字段的值
vLegCount := valueOfDog.FieldByName("legCount")
//嘗試設(shè)置legCount的值(這里會(huì)發(fā)生崩潰)
vLegCount.SetInt(4)
}
程序發(fā)生崩潰卧抗,報(bào)錯(cuò):
panic: reflect: reflect.Value.SetInt using value obtained using unexported field
報(bào)錯(cuò)的意思是:SetInt() 使用的值來自于一個(gè)未導(dǎo)出的字段藤滥。
為了能修改這個(gè)值,需要將該字段導(dǎo)出社裆。將 dog 中的 legCount 的成員首字母大寫拙绊,導(dǎo)出 LegCount 讓反射可以訪問,修改后的代碼如下:
package main
import (
"fmt"
"reflect"
)
func main() {
type dog struct {
LegCount int
}
//獲取dog實(shí)例的反射值對(duì)象
valueOfDog := reflect.ValueOf(&dog{})
//// 取出dog實(shí)例地址的元素
valueOfDog = valueOfDog.Elem()
//獲取legCount字段的值
vLegCount := valueOfDog.FieldByName("LegCount")
//嘗試設(shè)置legCount的值
vLegCount.SetInt(4)
fmt.Println(vLegCount.Int())
}
代碼輸出如下:
4
代碼說明如下:
- 第 10 行,將 LegCount 首字母大寫導(dǎo)出該字段时呀。
- 第 15 行张漂,獲取 dog 實(shí)例指針的反射值對(duì)象。
- 第 19 行谨娜,取 dog 實(shí)例的指針元素航攒,也就是 dog 的實(shí)例。
- 第 21 行趴梢,取 dog 結(jié)構(gòu)體中 LegCount 字段的成員值漠畜。
- 第 24 行,修改該成員值坞靶。
- 第 26 行憔狞,打印該成員值。
值的修改從表面意義上叫可尋址彰阴,換一種說法就是值必須“可被設(shè)置”瘾敢。那么,想修改變量值尿这,一般的步驟是:
- 取這個(gè)變量的地址或者這個(gè)變量所在的結(jié)構(gòu)體已經(jīng)是指針類型簇抵。
- 使用 reflect.ValueOf 進(jìn)行值包裝。
- 通過 Value.Elem() 獲得指針值指向的元素值對(duì)象(Value)射众,因?yàn)橹祵?duì)象(Value)內(nèi)部對(duì)象為指針時(shí)碟摆,使用 set 設(shè)置時(shí)會(huì)報(bào)出宕機(jī)錯(cuò)誤。
- 使用 Value.SetXXX 設(shè)置值叨橱。
通過類型信息創(chuàng)建實(shí)例
當(dāng)已知 reflect.Type 時(shí)典蜕,可以動(dòng)態(tài)地創(chuàng)建這個(gè)類型的實(shí)例,實(shí)例的類型為指針罗洗。例如 reflect.Type 的類型為 int 時(shí)愉舔,創(chuàng)建 int 的指針,即*int
伙菜,代碼如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
//取變量a的反射類型對(duì)象
typeOfA := reflect.TypeOf(a)
//根據(jù)反射類型對(duì)象創(chuàng)建類型實(shí)例
aIns := reflect.New(typeOfA)
//輸出Value的類型和種類
fmt.Println(aIns.Type(), aIns.Kind())
}
代碼輸出結(jié)果如下
*int ptr
代碼說明如下:
- 第 13 行轩缤,獲取變量 a 的反射類型對(duì)象。
- 第 16 行仇让,使用 reflect.New() 函數(shù)傳入變量 a 的反射類型對(duì)象,創(chuàng)建這個(gè)類型的實(shí)例值躺翻,值以 reflect.Value 類型返回丧叽。這步操作等效于:new(int),因此返回的是 *int 類型的實(shí)例公你。
- 第 19 行踊淳,打印 aIns 的類型為 *int,種類為指針。
通過反射調(diào)用函數(shù)
如果反射值對(duì)象(reflect.Value)中值的類型為函數(shù)時(shí)迂尝,可以通過 reflect.Value 調(diào)用該函數(shù)脱茉。使用反射調(diào)用函數(shù)時(shí),需要將參數(shù)使用反射值對(duì)象的切片 []reflect.Value 構(gòu)造后傳入 Call() 方法中垄开,調(diào)用完成時(shí)琴许,函數(shù)的返回值通過 []reflect.Value 返回。
下面的代碼聲明一個(gè)加法函數(shù)溉躲,傳入兩個(gè)整型值榜田,返回兩個(gè)整型值的和。將函數(shù)保存到反射值對(duì)象(reflect.Value)中锻梳,然后將兩個(gè)整型值構(gòu)造為反射值對(duì)象的切片([]reflect.Value)箭券,使用 Call() 方法進(jìn)行調(diào)用。
反射調(diào)用函數(shù):
package main
import (
"fmt"
"reflect"
)
//普通函數(shù)
func add(a, b int) int {
return a + b
}
func main() {
//將函數(shù)包裝為反射值對(duì)象
funcValue := reflect.ValueOf(add)
//構(gòu)造函數(shù)參數(shù)疑枯,傳入兩個(gè)整形值
paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}
//反射調(diào)用函數(shù)
retList := funcValue.Call(paramList)
fmt.Println(retList[0].Int())
}
代碼說明如下:
- 第 9~12 行辩块,定義一個(gè)普通的加法函數(shù)。
- 第 17 行荆永,將 add 函數(shù)包裝為反射值對(duì)象废亭。
- 第 20 行,將 10 和 20 兩個(gè)整型值使用 reflect.ValueOf 包裝為 reflect.Value屁魏,再將反射值對(duì)象的切片 []reflect.Value 作為函數(shù)的參數(shù)滔以。
- 第 23 行,使用 funcValue 函數(shù)值對(duì)象的 Call() 方法氓拼,傳入?yún)?shù)列表 paramList 調(diào)用 add() 函數(shù)你画。
- 第 26 行,調(diào)用成功后桃漾,通過 retList[0] 取返回值的第一個(gè)參數(shù)坏匪,使用 Int 取返回值的整數(shù)值。
提示
反射調(diào)用函數(shù)的過程需要構(gòu)造大量的 reflect.Value 和中間變量撬统,對(duì)函數(shù)參數(shù)值進(jìn)行逐一檢查适滓,還需要將調(diào)用參數(shù)復(fù)制到調(diào)用函數(shù)的參數(shù)內(nèi)存中。調(diào)用完畢后恋追,還需要將返回值轉(zhuǎn)換為 reflect.Value凭迹,用戶還需要從中取出調(diào)用值。因此苦囱,反射調(diào)用函數(shù)的性能問題尤為突出嗅绸,不建議大量使用反射函數(shù)調(diào)用。
通過反射調(diào)用方法
調(diào)用方法和調(diào)用函數(shù)是一樣的撕彤,只不過結(jié)構(gòu)體需要先通過rValue.Method()先獲取方法再調(diào)用鱼鸠,請(qǐng)看如下示例:
package main
import (
"fmt"
"reflect"
)
type MyMath struct {
Pi float64
}
//普通函數(shù)
func (myMath MyMath) Sum(a, b int) int {
return a + b
}
func (myMath MyMath) Dec(a, b int) int {
return a - b
}
func main() {
var myMath = MyMath{Pi:3.14159}
//獲取myMath的值對(duì)象
rValue := reflect.ValueOf(myMath)
//獲取到該結(jié)構(gòu)體有多少個(gè)方法
//numOfMethod := rValue.NumMethod()
//構(gòu)造函數(shù)參數(shù),傳入兩個(gè)整形值
paramList := []reflect.Value{reflect.ValueOf(30), reflect.ValueOf(20)}
//調(diào)用結(jié)構(gòu)體的第一個(gè)方法Method(0)
//注意:在反射值對(duì)象中方法索引的順序并不是結(jié)構(gòu)體方法定義的先后順序
//而是根據(jù)方法的ASCII碼值來從小到大排序,所以Dec排在第一個(gè)蚀狰,也就是Method(0)
result := rValue.Method(0).Call(paramList)
fmt.Println(result[0].Int())
}
代碼輸出結(jié)果為:
10