golang的反射機制與實踐(上)

寫在前面

反射機制是一個很重要的內容,當咱們寫框架的時候,要想要鬆耦合,高複用,那麼就有不少地方都須要用到反射,可謂是中高級程序員必須掌握的知識點程序員

不少後臺語言都有反射機制,但它們的使用原理大多都是同樣的微信

各語言不一樣的地方,大體就是代碼實現方式不一致罷了框架

其根本,都是從變量獲得反射對象,再由反射對象去操做原變量函數

好了,步入正題單元測試

什麼是反射

我就用一句話來歸納吧學習

使用反射,可讓咱們在程序運行時對任意類型的對象進行操做測試

注意操做這兩個字,操做是指:能夠獲取對象的信息、改變對象的值、調用對象的方法、甚至是建立一個對象spa

說到這你可能有點困惑,咱們在編寫代碼的時候不就已經把該實例化的象進行了實例化,該調用的方法都調用了嘛?爲何寫程序的時候不調用方法,偏要在運行時去進行這些操做?指針

其實問題就在這裏,若是咱們在寫程序的時候,一切的對象與方法都可以肯定了,那還要反射作什麼?code

正是由於咱們在寫程序的時候,要想寫一些「萬能程序」,用於下降代碼的耦合度,因此咱們才須要反射,用於處理一些未知的對象

想一想,當咱們寫一個方法,無論別人往咱們這個方法內傳入什麼樣的參數,最後咱們的函數都能給別人所須要的內容。是否是感受很牛逼?

反射的使用原理

我這裏主要說使用反射的原理,並非刨析反射的底層原理,有興趣想要探索原理的讀者大人,能夠去看看go的reflect包源碼

先給大家上個圖,看懂這個關係圖,後面的文字基本也就能夠不看了

沒看懂不要緊,稍微解釋就能明白~~

咱們定義的一個變量,不論是基本類型int,仍是一個結構體Employee,咱們均可以經過reflect.TypeOf()獲取他的反射類型Type,也能夠經過reflect.ValueOf()去獲取他的反射值Value

咱們學習反射,其實就是學習如何使用原變量,去取得reflect.Type或者reflect.Value這種反射對象;再使用這個反射對象Type以及Value,反過來對原變量進行操做

弄明白了這個道理,那一切都將變得簡單

剩下的,咱們只是須要去學習reflect包中提供的方法。當咱們須要要怎麼操做變量,就使用其提供的對應方法便可

反射的注意事項與細節

TypeKind的區別是什麼?

Type是類型,Kind是類別,聽起來有點繞,他們之間的關係爲TypeKind的子集

若是變量是基本類型,那麼TypeKind獲得的結果是一致的,好比變量爲int類型,TypeKind的值相等,都爲int

但當變量爲結構體時,TypeKind的值就不同了

咱們來看個實際案例

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之間切換?

變量能夠轉換成interface{}以後,再轉換成reflect.Value類型,既然空接口能夠轉換成Value類型,那麼天然也能夠反過來轉換成變量

用個表達式來表示,就以下所示

變量<----->interface{}<----->reflect.Value

利用空接口來進行中轉,這樣變量Value之間就能夠實現互相轉換了

下面咱們再說如何用代碼實現轉換

如何使用反射獲取變量自己的值?

這裏咱們要注意一下,reflect.ValueOf()獲得的值是reflect.Value類型,並非變量自己的值

var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
複製代碼

這段代碼會報錯invalid operation: num + rVal (mismatched types int and reflect.Value)

很明顯,rVal是屬於reflect.Value類型,不能與int類型相加

那怎樣才能得到它自己的值呢?

若是是基本類型,好比var num int,那麼使用reflect包裏提供的轉換方法便可reflect.ValueOf(num).Int()

或者是float,那就調用reflect.ValueOf(num).float(),若是是其它的基本類型,須要的時候去文檔裏面找找便可

若是是咱們本身定義的結構體,由於reflect包沒法肯定咱們本身定義了什麼結構體,因此自己並不會帶有結構體轉換的方法,那麼咱們只能經過類型斷言來進行轉換

也就是上面說的,利用空接口進行中轉,再利用斷言進行類型轉換,能夠看以下代碼示例

// Employee 員工
type Employee struct {
	Name string
	Age  int
}

func main() {
	emp := &Employee{
		Name: "naonao",
		Age:  99,
	}
	reflectPrint(emp)
}

func reflectPrint(v interface{}) {
	rVal := reflect.ValueOf(v)   // 獲取reflect.Value
	iV := rVal.Interface()       // 利用空接口進行中轉
	empVal, ok := iV.(*Employee) // 利用斷言轉換
	if ok {
		// 若是成功轉換則打印結構體
		log.Print(empVal)
	}
}

複製代碼

這裏我只是進行了一個簡單的判斷,若是想要進行完整的判斷,仍是須要藉助swith語句,下篇會提到。也能夠參照reflect包的單元測試文件

經過反射來修改變量

先來看看代碼如何實現

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)
}
複製代碼

細心的你確定發現了一點異常,函數接收的參數再也不是值了,而是接受了一個指針地址

改變值的時候,先調用了Elem()方法,再進行了一個SetInt()的操做

爲何直接傳值不行呢?由於reflect包中提供的全部修改變量值的方法,都是對指針進行的操做

那爲何還要先使用Elem()呢?由於Elem()的做用,就是取得指針地址所對應的值,取到值了,咱們才能對值進行修改

總不可能連值都沒拿到手,就想着去改值吧?

如何理解reflect.Value.Elem()

關於Elem()的使用能夠簡單的理解爲

num := 1
prt *int := &num // 獲取num的指針地址
num2 := *ptr // 從指針處取值
複製代碼

由於咱們傳遞了一個地址,因此咱們要先拿到這個地址的指針,再經過指針去取得所對應的值

reflect包底層實現就是基於這個原理,不過它的底層代碼加了較多的判斷,用來保證穩定性

寫在最後

這篇先說些基礎概念,下篇咱們再從實踐出發,看看在什麼地方須要使用反射,又該如何使用reflect包提供的方法去實現

微信掃碼關注公衆號「鬧鬧吃魚」,還可領取Go語言學習大禮包,入門到進階再也不無頭緒

相關文章
相關標籤/搜索