基本概念 支持反射的語言可以在程式編譯期將變數的反射信息,如欄位名稱、類型信息、結構體信息等整合到可執行文件中,並給程式提供介面訪問反射信息,這樣就可以在程式運行期獲取類型的反射信息,並且有能力修改它們。 Go語言提供了 reflect 包來訪問程式的反射信息。 Refelct解析 Refelct包 ...
基本概念
支持反射的語言可以在程式編譯期將變數的反射信息,如欄位名稱、類型信息、結構體信息等整合到可執行文件中,並給程式提供介面訪問反射信息,這樣就可以在程式運行期獲取類型的反射信息,並且有能力修改它們。
Go語言提供了 reflect 包來訪問程式的反射信息。
Refelct解析
Refelct包 定義了兩個重要的類型 Type 和 Value,任意介面在反射中都可以理解為 由 reflect.Type 和 reflect.Value 兩部分組成 。簡單來說,go 的介面是由兩部分組成的,一部分是類型信息,另一部分是數據信息
eg
var a=1
var b interface{}=a
對於 這個例子,b 的類型信息是 int,數據信息是 1,這兩部分信息都是存儲在 b 裡面的。b 的記憶體結構如下:
而 b實際上是一個空介面,也就是說一個 interface{} 中實際上既包含了變數的類型信息,也包含了類型的數據
refelct.Type ,refelct.Value
如上所說,所有的介面都含有type 和value ,我們可以使用refelct包中的 typeof 和valueof將信息從介面中取出
var a = 1
t := reflect.TypeOf(a)
var b = "hello"
t1 := reflect.ValueOf(b)
反射定律
三條反射定律 :
- 反射可以將 interface 類型變數轉換成反射對象。
- 反射可以將反射對象還原成 interface 對象。
- 如果要修改反射對象,那麼反射對象必須是可設置的(CanSet)。
將 interface 類型變數轉換成反射對象
我們可以通過 reflect.TypeOf 和 reflect.ValueOf 來獲取到一個變數的反射類型和反射值。
var a = 1
typeOfA := reflect.TypeOf(a)
valueOfA := reflect.ValueOf(a)
將反射對象還原成 interface 對象。
我們可以通過 reflect.Value.Interface 來獲取到反射對象的 interface 對象,也就是傳遞給 reflect.ValueOf 的那個變數本身。 不過返回值類型是 interface{},所以我們需要進行類型斷言。
i := valueOfA.Interface()
fmt.Println(i.(int))
修改反射對象
通過 reflect.Value.CanSet 來判斷一個反射對象是否是可設置的。如果是可設置的,我們就可以通過 reflect.Value.Set 來修改反射對象的值。
var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println("settability of v:", v.CanSet()) // false
fmt.Println("settability of v:", v.Elem().CanSet()) // true
那什麼情況下一個反射對象是可設置的呢?前提是這個反射對象是一個指針,然後這個指針指向的是一個可設置的變數 .
在上面這個例子中,v.CanSet() 返回的是 false,而 v.Elem().CanSet() 返回的是 true。
在這裡,v是一根指針,但是v.Elem()才是v這根指針指向的值。Elem方法是一個解引用的作用。對於這個指針本身,我們修改它是沒有意義的,我們可以設想一下, 如果我們修改了指針變數(也就是修改了指針變數指向的地址),那會發生什麼呢?那樣我們的指針變數就不是指向 x 了, 而是指向了其他的變數,這樣就不符合我們的預期了。所以 v.CanSet() 返回的是 false。
而 v.Elem().CanSet() 返回的是 true。這是因為 v.Elem() 才是 x 本身,通過 v.Elem() 修改 x 的值是沒有問題的
Elem()
refelct.Value中的Elem
reflect.Value 的 Elem 方法的作用是獲取指針指向的值,或者獲取介面的動態值。
對於指針很好理解,其實作用類似解引用。而對於介面,還是要回到 interface 的結構本身,因為介面里包含了類型和數據本身,所以 Elem 方法就是獲取介面的數據部分(也就是 iface 或 eface 中的 data 欄位)。
refelct.Type中的Elem
reflect.Type 的 Elem 方法的作用是獲取數組、chan、map、指針、切片關聯元素的類型信息,也就是說,對於 reflect.Type 來說, 能調用 Elem 方法的反射對象,必須是數組、chan、map、指針、切片中的一種,其他類型的 reflect.Type 調用 Elem 方法會 panic。
t1 := reflect.TypeOf([3]int{1, 2, 3}) // 數組 [3]int
fmt.Println(t1.String()) // [3]int
fmt.Println(t1.Elem().String()) // int
需要註意的是,如果我們要獲取 map 類型 key 的類型信息,需要使用 Key 方法,而不是 Elem 方法。
m := make(map[string]string)
t1 := reflect.TypeOf(m)
fmt.Println(t1.Key().String()) // string