我們定義的一個(gè)變量,不管是基本類型int,還是一個(gè)結(jié)構(gòu)體Student,我們都可以通過(guò)reflect.TypeOf()獲取他的反射類型Type苍凛,也可以通過(guò)reflect.ValueOf()去獲取他的反射值Value
我們學(xué)習(xí)反射,其實(shí)就是學(xué)習(xí)如何通過(guò)變量兵志,去取得reflect.Type或者reflect.Value醇蝴;再使用得到的Type以及Value,反過(guò)來(lái)對(duì)變量進(jìn)行操作
弄明白了這個(gè)道理想罕,那一切都將變得簡(jiǎn)單
剩下的悠栓,我們只是需要去學(xué)習(xí)reflect包中提供的方法。當(dāng)我們需要要怎么操作變量按价,就使用其提供的對(duì)應(yīng)方法即可
反射的注意事項(xiàng)與細(xì)節(jié)
Type與Kind的區(qū)別
Type是類型惭适,Kind是類別,聽(tīng)起來(lái)有點(diǎn)繞楼镐,他們之間的關(guān)系為Type是Kind的子集
如果變量是基本類型癞志,那么Type與Kind得到的結(jié)果是一致的,比如變量為int類型框产,Type與Kind的值相等凄杯,都為int
但當(dāng)變量為結(jié)構(gòu)體時(shí),Type與Kind的值就不一樣了
我們來(lái)看個(gè)實(shí)際案例
func main() {
var emp Employee
emp = Employee{
Name: "naonao",
Age: 99,
}
rVal := reflect.ValueOf(emp)
log.Printf("Kind is %v ,Type is %v",
rVal.Kind(),
rVal.Type())
// Kind is struct ,Type is main.Employee
}
可以看到秉宿,Kind的值是struct,而Type的值是包名.Employee
在變量與reflect.Value
之間切換
變量可以轉(zhuǎn)換成interface{}之后戒突,再轉(zhuǎn)換成reflect.Value類型,既然空接口可以轉(zhuǎn)換成Value類型描睦,那么自然也可以反過(guò)來(lái)轉(zhuǎn)換成變量
用個(gè)表達(dá)式來(lái)表示膊存,就如下所示
變量<----->interface{}<----->reflect.Value
利用空接口來(lái)進(jìn)行中轉(zhuǎn),這樣變量與Value之間就可以實(shí)現(xiàn)互相轉(zhuǎn)換了
下面我們?cè)僬f(shuō)如何用代碼實(shí)現(xiàn)轉(zhuǎn)換
通過(guò)反射獲取變量本身的值
這里我們要注意一下,reflect.ValueOf()得到的值是reflect.Value類型隔崎,并不是變量本身的值
var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
這段代碼會(huì)報(bào)錯(cuò)invalid operation: num + rVal (mismatched types int and reflect.Value)
很明顯今艺,rVal是屬于reflect.Value類型,不能與int類型相加
那怎樣才能獲得它本身的值呢爵卒?
如果是基本類型虚缎,比如var num int,那么使用reflect包里提供的轉(zhuǎn)換方法即可reflect.ValueOf(num).Int()
或者是float,那就調(diào)用reflect.ValueOf(num).float()技潘,如果是其它的基本類型,需要的時(shí)候去文檔里面找找即可
但如果是我們自己定義的結(jié)構(gòu)體千康,因?yàn)閞eflect包無(wú)法確定我們自己定義了什么結(jié)構(gòu)體享幽,所以本身并不會(huì)帶有結(jié)構(gòu)體轉(zhuǎn)換的方法,那么我們只能通過(guò)類型斷言來(lái)進(jìn)行轉(zhuǎn)換
也就是上面說(shuō)的拾弃,利用空接口進(jìn)行中轉(zhuǎn)值桩,再利用斷言進(jìn)行類型轉(zhuǎn)換
使用反射來(lái)遍歷結(jié)構(gòu)體的字段值,并獲取結(jié)構(gòu)體的tag標(biāo)簽
// Employee 員工
type Employee struct {
Name string `json:"emp_name"`
Age int `json:"emp_age"`
Sex int
}
// GetSum 返回兩數(shù)之和
func (e *Employee) GetSum(n1, n2 int) int {
return n1 + n2
}
// Set 接受值豪椿,給結(jié)構(gòu)體e賦值
func (e *Employee) Set(name string, age, sex int) {
e.Name = name
e.Age = age
e.Sex = sex
}
// Print 打印結(jié)構(gòu)體*Employee
func (e *Employee) Print() {
log.Print("======Start======")
log.Print(e)
log.Print("======End======")
}
//使用反射來(lái)遍歷結(jié)構(gòu)體的字段值奔坟,并獲取結(jié)構(gòu)體的tag標(biāo)簽
//先來(lái)看個(gè)常規(guī)用法
// GetStruct 獲取結(jié)構(gòu)體的字段及tag
func GetStruct(i interface{}) {
rType := reflect.TypeOf(i)
rVal := reflect.ValueOf(i)
kd := rVal.Kind()
// 如果是傳進(jìn)來(lái)的是指針類型
// 則獲取指針值
if kd == reflect.Ptr {
rType = rType.Elem()
rVal = rVal.Elem()
kd = rVal.Kind()
}
if kd != reflect.Struct {
log.Panicf("Kind is %v not struct ", kd)
}
// 獲取結(jié)構(gòu)體的字段數(shù)
sNum := rVal.NumField()
log.Printf("Struct has %v fields ", sNum)
// 遍歷結(jié)構(gòu)體的所有字段
for i := 0; i < sNum; i++ {
log.Printf("Field %d value is %v", i, rVal.Field(i))
// 獲取Struct的tag,使用Type類型獲取
tag := rType.Field(i).Tag.Get("json")
if tag == "" {
log.Printf("Field %d hasn't tag %v ", i, tag)
continue
}
log.Printf("Field %d tag is %v ", i, tag)
}
}
//復(fù)制代碼我們定義一個(gè)方法GetStruct(i interface{})搭盾,因?yàn)槿雲(yún)⑹莍nterface{}類型咳秉,所以這個(gè)方法可以接收
//并處理所有的數(shù)據(jù)類型。這就是反射的牛逼之處了
//遺憾的是鸯隅,反射的性能比較低澜建。后面咱們對(duì)性能進(jìn)行分析時(shí)再拿出來(lái)聊聊
//測(cè)試用例如下
func TestGetStruct(t *testing.T) {
emp := &Employee{}
emp.Set("鬧鬧", 99, 0)
GetStruct(emp)
}
//這個(gè)函數(shù)接受的參數(shù)是interface,也就是說(shuō)蝌以,通過(guò)這個(gè)函數(shù)炕舵,不管入?yún)鬟f了什么樣的結(jié)構(gòu)體,我們可以知道這個(gè)結(jié)構(gòu)體有什么標(biāo)簽跟畅,
//有幾個(gè)方法獲取tag標(biāo)簽的用處就是對(duì)我們的結(jié)構(gòu)體進(jìn)行序列化時(shí)使用咽筋,將結(jié)構(gòu)體的字段名變成我們需要的別名
//想深入了解的童鞋,可以參考下encoding/json包的使用方式
獲取并調(diào)用結(jié)構(gòu)體的方法
// CallMethod 調(diào)用結(jié)構(gòu)體方法
// i : 傳入的struct
// methodByName : 調(diào)用結(jié)構(gòu)體的方法名
func CallMethod(i interface{}, methodByName string) {
rVal := reflect.ValueOf(i)
rType := reflect.TypeOf(i)
log.Printf("Type is %v Kind is %v", rType, rType.Kind())
// 獲取結(jié)構(gòu)體有多少個(gè)方法
numOfMethod := rVal.NumMethod()
log.Printf("Struct has %d method", numOfMethod)
// 聲明Value數(shù)組
var params []reflect.Value
// 聲明一個(gè)Value類型徊件,用于接收方法
var method reflect.Value
if methodByName == "GetSum" {
// 調(diào)用方法時(shí)的參數(shù)
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(88))
}
if methodByName == "Set" {
// 調(diào)用方法時(shí)的參數(shù)
params = append(params, reflect.ValueOf("鬧鬧吃魚(yú)"))
params = append(params, reflect.ValueOf(18))
params = append(params, reflect.ValueOf(0))
}
// 獲取方法
method = rVal.MethodByName(methodByName)
if !method.IsValid() {
// 如果結(jié)構(gòu)體不存在此方法奸攻,輸出Panic
log.Panic("Method is invalid")
}
result := method.Call(params)
if len(result) > 0 {
// 如果函數(shù)存在返回值,則打印第一條
log.Println("Call result is ", result[0])
}
}
//這里值得注意一點(diǎn)的就是虱痕,我們通過(guò)反射的Call去調(diào)用函數(shù)舞箍,傳入的參數(shù)的類型是reflect.Value類型,并不是我們定義函數(shù)時(shí)的int類型
//所以在調(diào)用函數(shù)時(shí)傳入的參數(shù)需要進(jìn)行一個(gè)類型轉(zhuǎn)換
//給你們附上測(cè)試用例皆疹,你們可以自己調(diào)試跑跑疏橄,會(huì)發(fā)現(xiàn),不管你傳的結(jié)構(gòu)體的字段是什么,我都進(jìn)行統(tǒng)一處理了
func TestCallMethod(t *testing.T) {
emp := &Employee{}
emp.Set("鬧鬧", 99, 0)
emp.Print()
CallMethod(emp, "Set")
emp.Print()
}
修改字段值
// ModifyField 修改字段值
func ModifyField(i interface{}, filedName string) {
rVal := reflect.ValueOf(i)
filed := rVal.Elem().FieldByName(filedName)
if !filed.IsValid() {
log.Panic("filedName is invalid")
}
filed.SetString("鬧鬧")
}
復(fù)制代碼運(yùn)行時(shí)修改結(jié)構(gòu)體的字段捎迫,主要就是做到一個(gè)通用性晃酒,比如上述的例子,不管是什么結(jié)構(gòu)體
依然附上測(cè)試用例
func TestModifyField(t *testing.T) {
emp := &Employee{}
ModifyField(emp, "Name")
}
復(fù)制代碼不管傳入的結(jié)構(gòu)體是什么窄绒,只要包含了filedName(我們指定的字段名)贝次,我們就可以對(duì)其進(jìn)行值的更改
假如我們有100個(gè)結(jié)構(gòu)體,需要對(duì)name字段進(jìn)行修改彰导,通過(guò)反射的機(jī)制蛔翅,我們代碼的耦合度將大大的降低
定義適配器,用作統(tǒng)一處理接口
// Bridge 適配器
// 可以實(shí)現(xiàn)調(diào)用任意函數(shù)
func Bridge(call interface{}, args ...interface{}) {
var (
function reflect.Value
inValue []reflect.Value
)
n := len(args)
// 將參數(shù)轉(zhuǎn)換為Value類型
inValue = make([]reflect.Value, n)
for i := 0; i < n; i++ {
inValue[i] = reflect.ValueOf(args[i])
}
// 獲得函數(shù)的Value類型
function = reflect.ValueOf(call)
// 傳參位谋,調(diào)用函數(shù)
function.Call(inValue)
}
//寫了個(gè)測(cè)試用例山析,函數(shù)是我們?cè)谡{(diào)用Bridge前就已經(jīng)定義好了
func TestBridge(t *testing.T) {
call1 := func(v1, v2 int) {
log.Println(v1, v2)
}
call2 := func(v1, v2 int, str string) {
log.Println(v1, v2, str)
}
Bridge(call1, 1, 2)
Bridge(call2, 2, 3, "callTest")
}
//兩個(gè)函數(shù)是不同的函數(shù),但是都可以通過(guò)Bridge進(jìn)行執(zhí)行
//適配器有什么用呢掏父?如果不知道的童鞋笋轨,可以去看看設(shè)計(jì)模式「適配器模式」
//因?yàn)楸酒皇钦f(shuō)如何在實(shí)戰(zhàn)中應(yīng)用反射,所以這里就不講解設(shè)計(jì)模式了
//使用反射創(chuàng)建赊淑,并操作結(jié)構(gòu)體
// CreateStruct 使用反射創(chuàng)建結(jié)構(gòu)體
// 并給結(jié)構(gòu)體賦值
func CreateStruct(i interface{}) *Employee {
var (
structType reflect.Type
structValue reflect.Value
)
// 獲取傳入結(jié)構(gòu)體指向的Type類型
structType = reflect.TypeOf(i).Elem()
// 創(chuàng)建一個(gè)結(jié)構(gòu)體
// structValue持有一個(gè)指向類型為Type的新申請(qǐng)的指針
structValue = reflect.New(structType)
// 轉(zhuǎn)換成我們要?jiǎng)?chuàng)建的結(jié)構(gòu)體
modle := structValue.Interface().(*Employee)
// 取得structValue指向的值
structValue = structValue.Elem()
// 給結(jié)構(gòu)體賦值
structValue.FieldByName("Name").SetString("鬧鬧吃魚(yú)")
structValue.FieldByName("Age").SetInt(100)
return modle
}
//使用方式就看看測(cè)試用例
func TestCreateStruct(t *testing.T) {
emp := &Employee{
Name: "NaoNao",
Age: 18,
Sex: 1,
}
emp.Print()// &{NaoNao 18 1}
newEmp := CreateStruct(emp)
newEmp.Print()// &{鬧鬧吃魚(yú) 100 0}
}
//可能你會(huì)問(wèn)爵政,CreateStruct的入?yún)⒉皇莍nterface{}嗎?可為什么我傳一個(gè)任意的結(jié)構(gòu)體陶缺,卻要給返回一個(gè)指定的結(jié)構(gòu)體呢钾挟?就不能我傳什么結(jié)構(gòu)體進(jìn)去,就返回什么結(jié)構(gòu)體出來(lái)嗎饱岸?
//理想總是豐滿的等龙,現(xiàn)實(shí)卻是非常骨感,雖然我們反射的方法實(shí)現(xiàn)伶贰,都是將入?yún)憺閕nterface{}蛛砰,但使用反射并不是意味著我們一定就寫了一個(gè)萬(wàn)能的程序
//我們不管是把Value轉(zhuǎn)為結(jié)構(gòu)體,還是轉(zhuǎn)為基本類型黍衙,我們都需要在編譯前確定轉(zhuǎn)換后的類型
//換句話說(shuō)泥畅,只要我們?cè)谶\(yùn)行時(shí)牽扯到類型的轉(zhuǎn)換,我們都需要各種if來(lái)判斷是否能轉(zhuǎn)換成我們需要的類型
通過(guò)反射來(lái)修改變量
先來(lái)看看代碼如何實(shí)現(xiàn)
func main() {
var num = 1
modifyValue(&num)// 傳遞地址
log.Printf("num is %v", num)// num is 20
}
func modifyValue(i interface{}) {
rVal := reflect.ValueOf(i)
rVal.Elem().SetInt(20)
}
細(xì)心的你肯定發(fā)現(xiàn)了一點(diǎn)異常琅翻,函數(shù)接收的參數(shù)不再是值了位仁,而是接受了一個(gè)指針地址
改變值的時(shí)候,先調(diào)用了Elem()方法方椎,再進(jìn)行了一個(gè)SetInt()的操作
為什么直接傳值不行呢聂抢?因?yàn)閞eflect包中提供的所有修改變量值的方法,都是對(duì)指針進(jìn)行的操作
那為什么還要先使用Elem()呢棠众?因?yàn)镋lem()的作用琳疏,就是取得指針地址所對(duì)應(yīng)的值有决,取到值了,我們才能對(duì)值進(jìn)行修改
總不可能連值都沒(méi)拿到手空盼,就想著去改值吧书幕?
理解reflect.Value.Elem()
關(guān)于Elem()的使用可以簡(jiǎn)單的理解為
num := 1
prt *int := &num // 獲取num的指針地址
num2 := *ptr // 從指針處取值
因?yàn)槲覀儌鬟f了一個(gè)地址,所以我們要先拿到這個(gè)地址的指針揽趾,再通過(guò)指針去取得所對(duì)應(yīng)的值
reflect包底層實(shí)現(xiàn)就是基于這個(gè)原理台汇,只是它的底層代碼加了較多的判斷,用來(lái)保證穩(wěn)定性