概念:
官方對此有個非常簡明的介紹,兩句話耐人尋味:
- 反射提供一種讓程序檢查自身結構的能力
- 反射是困惑的源泉
第1條,再精確點的描述是“反射是一種檢查interface變量的底層類型和值的機制”。 第2條,很有喜感的自嘲,不過往后看就笑不出來了,因為你很可能產生困惑。
reflect 實現了運行時的反射能力,能夠讓程序操作不同類型的對象。反射包中有兩對非常重要的函數和類型,兩個函數分別是:
- reflect.TypeOf()? ?能獲取類型信息;
- reflect.ValueOf()?能獲取數據的運行時表示;
只有這么簡單嗎?當然不是,請繼續閱讀。
引出:
其實了解反射的第一步,應從interface入手,因為反射與接口存在著千絲萬縷的關系。
如下是一段interface的源碼
type iface struct {tab *itabdata unsafe.Pointer
}// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.
type itab struct {inter *interfacetype_type *_typelink *itabbad int32inhash int32 // has this itab been added to hash?fun [1]uintptr // variable sized
}
看不懂也沒關系,我對其大致簡化一番,從reflect角度再來看看,并思考從iface中看到的字段:
type I interface{// 方法集
}
type iface struct{typ reflect.Type // 儲存類型信息val reflect.Value // 儲存實際值
}
之所以引出interface,是因為想說interface類型有個(value,type)對,而反射就是檢查interface的這個(value, type)對的。具體一點說就是Go提供一組方法提取interface的value,提供另一組方法提取interface的type。
- reflect.Type 提供一組接口處理interface的類型,即(value, type)中的type
- reflect.Value 提供一組接口處理interface的值,即(value, type)中的value
下面會提到反射對象,所謂反射對象即反射包里提供的兩種類型的對象。
- reflect.Type 類型對象
- reflect.Value 類型對象
三大法則:
第一法則:
從 interface{}?變量,可以反射出反射對象;
下面示例,看看是如何通過反射獲取一個變量的值和類型的:
package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4t := reflect.TypeOf(x) //t is reflext.Typefmt.Println("type:", t)v := reflect.ValueOf(x) //v is reflext.Valuefmt.Println("value:", v)
}運行如下:
type: float64
value: 3.4
是不是疑惑了,明明是上述是x->reflect類型,卻依然說是 interface{} --變為--> reflect類型呢?這是因為,在TypeOf 與 ValueOf 內部,自動將 值類型,轉化為了 接口類型。
?
第二法則:
從反射對象可以獲取?interface{}?變量;
package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4v := reflect.ValueOf(x) //v is reflext.Valuevar y float64 = v.Interface().(float64)fmt.Println("value:", y)
}
1、用reflect.ValueOf(x) 獲取,value值。
2、v.Interface() 轉化成接口。
3、類型斷言轉化成,對應的基本類型
?
第三法則:
要修改反射對象,其值必須可設置。
通過反射可以將interface類型變量轉換成反射對象,可以使用該反射對象設置其持有的值。在介紹何謂反射對象可修改前,先看一下失敗的例子:
package mainimport ("reflect"
)func main() {var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) // Error: will panic.
}如下代碼,通過反射對象v設置新值,會出現panic。報錯如下:panic: reflect: reflect.Value.SetFloat using unaddressable value
錯誤原因即是v是不可修改的。
反射對象失敗,取決于是否可以修改其儲存的值。回想一下函數傳參時,是傳值還是傳址,就不難理解上例中為何失敗。
上例中,傳入 reflect.ValueOf() 函數的其實是x的值,而非x本身。即通過v修改其值是無法影響x的,也即是無效的修改,所以 golang 會報錯。
想到此處,即可明白,如果構建v時使用x的地址就可實現修改了,但此時v代表的是指針地址,我們要設置的是指針所指向的內容,也即我們想要修改的是*v
。 那怎么通過v修改x的值呢?
reflect.Value 提供了 Elem() 方法,可以獲得指針向指向的Value 。看如下代碼:
package mainimport (
"reflect""fmt"
)func main() {var x float64 = 3.4v := reflect.ValueOf(&x)v.Elem().SetFloat(7.1)fmt.Println("x :", v.Elem().Interface())
}
1、調用reflect.ValueOf 獲取變量指針。
2、調用 reflect.Value.Elem 獲取指針指向的變量。
3、調用 reflect.Value.SetFloat() 更新變量。
總結:
以上為本篇博客精華內容,如有不妥,請及時私信聯系我,斟酌之后必加以糾正。
待后續深入學習時,會轉回繼續修改。
參考內容:
1、《Go專家編程》
2、《Go語言設計與實踐》
?