反射是指計算機程序在運行時(Run time)能夠訪問、檢測和修改它自己狀態或行爲的一種能力。用比喻來講,反射就是程序在運行的時候可以「觀察」而且修改本身的行爲。利用 Go 語言的反射機制,能夠獲取結構體的公有字段以及私有字段的標籤名,甚至一些其餘比較敏感的信息。html
1.有時編寫一個函數,可是並不知道傳給你的參數類型是什麼,多是沒約定好;也多是傳入的類型不少,這些類型並不能統一表示。這時反射就會用的上了。程序員
2.有時候須要根據某些條件決定調用哪一個函數,好比根據用戶的輸入來決定。這時就須要對函數和函數的參數進行反射,在運行期間動態地執行函數。golang
interface是Go 語言實現抽象的一個很是強大的工具。當向接口變量賦予一個實體類型的時候,接口會存儲實體的類型信息,反射就是經過接口的類型信息實現的,反射創建在類型的基礎上。數組
Go 語言在 reflect 包裏定義了各類類型,實現了反射的各類函數,經過它們能夠在運行時檢測類型的信息、改變類型的值。markdown
types和interface Go 語言中,每一個變量都有一個靜態類型,在編譯階段就肯定了的,好比 int, float64, []int 等等。注意,這個類型是聲明時候的類型,不是底層數據類型.例如:函數
type MyInt int
var i int
var j MyInt
複製代碼
儘管 i,j 的底層類型都是 int,但咱們知道,他們是不一樣的靜態類型,除非進行類型轉換,不然,i 和 j 不能同時出如今等號兩側。j 的靜態類型就是 MyInt工具
反射主要與 interface{} 類型相關 interface底層有兩種結構:帶接口的和不帶接口的oop
帶接口的interface底層結構以下:ui
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32
bad bool
inhash bool
unused [2]byte
fun [1]uintptr
}
複製代碼
itab:由具體類型 _type 以及 interfacetype 組成。_type 表示具體類型,而 interfacetype 則表示具體類型實現的接口類型, 也就是當前接口的類型。fun是一個接口列表,表示的是_type所表明的具體類型所實現的接口。spa
interfacetype表示的是當前接口變量的靜態類型,而itab._type表示的是當前接口變量的動態類型。當前接口變量可以調用的接口取決於它的靜態類型
不帶接口的interface底層結構以下:
type eface struct {
_type *_type
data unsafe.Pointer
}
複製代碼
eface 比較簡單了。只維護了一個 _type 字段,表示空接口所承載的具體的實體類型。data 描述了具體的值
使用簡單示例說明interface使用方法
首先定義兩個接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
複製代碼
接下來定義一段代碼:
var r io.Reader
tty, err := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
複製代碼
首先聲明 r 的類型是 io.Reader,注意,這是 r 的靜態類型,此時它的動態類型爲 nil,而且它的動態值也是 nil。
以後,r = tty 這一語句,將 r 的動態類型變成 *os.File,動態值則變成非空,表示打開的文件對象。這時,r 能夠用<value, type>對來表示爲: <tty, *os.File>。
此時雖然 fun 所指向的函數只有一個 Read 函數,其實 *os.File 還包含 Write 函數,也就是說 *os.File 其實還實現了 io.Writer 接口。所以下面的斷言語句能夠執行:
var w io.Writer
w = r.(io.Writer)
複製代碼
之因此用斷言,而不能直接賦值,是由於 r 的靜態類型是 io.Reader,並無實現 io.Writer 接口。斷言可否成功,看 r 的動態類型是否符合要求。其實r的真實類型*os.File實現了Write接口,所以是符合要求的。
這樣,w 也能夠表示成 <tty, *os.File>,僅管它和 r 同樣,可是 w 可調用的函數取決於它的靜態類型 io.Writer,也就是說它只能有這樣的調用形式: w.Write() 。
和 r 相比,僅僅是 fun 對應的函數變了:Read -> Write。
最後,再來一個賦值:
var empty interface{}
empty = w
複製代碼
因爲 empty 是一個空接口,所以全部的類型都實現了它,w 能夠直接賦給它,不須要執行斷言操做。
reflect 包裏定義了一個接口和一個結構體,即 reflect.Type 和 reflect.Value,它們提供不少函數來獲取存儲在接口裏的類型信息。
reflect.Type 主要提供關於類型相關的信息,因此它和 _type 關聯比較緊密;reflect.Value 則結合 _type 和 data 二者,所以程序員能夠獲取甚至改變類型的值。
reflect 包中提供了兩個基礎的關於反射的函數來獲取上述的接口和結構體:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
複製代碼
TypeOf函數:用來提取一個接口中值的類型信息。因爲它的輸入參數是一個空的interface,調用此函數時,實參會先被轉化爲 interface{}類型。這樣,實參的類型信息、方法集、值信息都存儲到 interface{} 變量裏了. TypeOf源碼以下:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
複製代碼
其中emptyInterface以下:
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
複製代碼
toType函數只是作了一個類型轉換:
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
複製代碼
返回值 Type 其實是一個接口,定義了不少方法,用來獲取類型相關的各類信息,而 *rtype 實現了 Type 接口:
type Type interface {
// 全部的類型均可以調用下面這些函數
// 此類型的變量對齊後所佔用的字節數
Align() int
// 若是是 struct 的字段,對齊後佔用的字節數
FieldAlign() int
// 返回類型方法集裏的第 `i` (傳入的參數)個方法
Method(int) Method
// 經過名稱獲取方法
MethodByName(string) (Method, bool)
// 獲取類型方法集裏導出的方法個數
NumMethod() int
// 類型名稱
Name() string
// 返回類型所在的路徑,如:encoding/base64
PkgPath() string
// 返回類型的大小,和 unsafe.Sizeof 功能相似
Size() uintptr
// 返回類型的字符串表示形式
String() string
// 返回類型的類型值
Kind() Kind
// 類型是否實現了接口 u
Implements(u Type) bool
// 是否能夠賦值給 u
AssignableTo(u Type) bool
// 是否能夠類型轉換成 u
ConvertibleTo(u Type) bool
// 類型是否能夠比較
Comparable() bool
// 下面這些函數只有特定類型能夠調用
// 如:Key, Elem 兩個方法就只能是 Map 類型才能調用
// 類型所佔據的位數
Bits() int
// 返回通道的方向,只能是 chan 類型調用
ChanDir() ChanDir
// 返回類型是不是可變參數,只能是 func 類型調用
// 好比 t 是類型 func(x int, y ... float64)
// 那麼 t.IsVariadic() == true
IsVariadic() bool
// 返回內部子元素類型,只能由類型 Array, Chan, Map, Ptr, or Slice 調用
Elem() Type
// 返回結構體類型的第 i 個字段,只能是結構體類型調用
// 若是 i 超過了總字段數,就會 panic
Field(i int) StructField
// 返回嵌套的結構體的字段
FieldByIndex(index []int) StructField
// 經過字段名稱獲取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名稱符合 func 函數的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 獲取函數類型的第 i 個參數的類型
In(i int) Type
// 返回 map 的 key 類型,只能由類型 map 調用
Key() Type
// 返回 Array 的長度,只能由類型 Array 調用
Len() int
// 返回類型字段的數量,只能由類型 Struct 調用
NumField() int
// 返回函數類型的輸入參數個數
NumIn() int
// 返回函數類型的返回值個數
NumOut() int
// 返回函數類型的第 i 個值的類型
Out(i int) Type
// 返回類型結構體的相同部分
common() *rtype
// 返回類型結構體的不一樣部分
uncommon() *uncommonType
}
複製代碼
ttype結構以下:
type rtype struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
}
複製代碼
全部的類型都會包含 rtype 這個字段,表示各類類型的公共信息;另外,不一樣類型包含本身的一些獨特的部分。正由於全部類型都包含了rtype字段,纔會有eface := *(*emptyInterface)(unsafe.Pointer(&i))
取到rtype
字段
好比下面的 arrayType 和 chanType 都包含 rytpe,而前者還包含 slice,len 等和數組相關的信息;後者則包含 dir 表示通道方向的信息。
// arrayType represents a fixed array type.
type arrayType struct {
rtype `reflect:"array"`
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
// chanType represents a channel type.
type chanType struct {
rtype `reflect:"chan"`
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
複製代碼
講完了 TypeOf 函數,再來看一下 ValueOf 函數。返回值 reflect.Value 表示 interface{} 裏存儲的實際變量,它能提供實際變量的各類信息。相關的方法經常是須要結合類型信息和值信息。例如,若是要提取一個結構體的字段信息,那就須要用到 _type (具體到這裏是指 structType) 類型持有的關於結構體的字段信息、偏移信息,以及 *data 所指向的內容 —— 結構體的實際值。
源碼以下:
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// ……
return unpackEface(i)
}
// 分解 eface
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
複製代碼
從源碼看,比較簡單:將先將 i 轉換成 *emptyInterface 類型, 再將它的 typ 字段和 word 字段以及一個標誌位字段組裝成一個 Value 結構體,而這就是 ValueOf 函數的返回值,它包含類型結構體指針、真實數據的地址、標誌位。
Value 結構體定義了不少方法,經過這些方法能夠直接操做 Value 字段 ptr 所指向的實際數據:
// 設置切片的 len 字段,若是類型不是切片,就會panic
func (v Value) SetLen(n int)
// 設置切片的 cap 字段
func (v Value) SetCap(n int)
// 設置字典的 kv
func (v Value) SetMapIndex(key, val Value)
// 返回切片、字符串、數組的索引 i 處的值
func (v Value) Index(i int) Value
// 根據名稱獲取結構體的內部字段值
func (v Value) FieldByName(name string) Value
// 用來獲取 int 類型的值
func (v Value) Int() int64
// 用來獲取結構體字段(成員)數量
func (v Value) NumField() int
// 嘗試向通道發送數據(不會阻塞)
func (v Value) TrySend(x reflect.Value) bool
// 經過參數列表 in 調用 v 值所表明的函數(或方法
func (v Value) Call(in []Value) (r []Value)
// 調用變參長度可變的函數
func (v Value) CallSlice(in []Value) []Value
複製代碼
另外,經過 Type() 方法和 Interface() 方法能夠打通 interface、Type、Value 三者。Type() 方法也能夠返回變量的類型信息,與 reflect.TypeOf() 函數等價。Interface() 方法能夠將 Value 還原成原來的 interface。
總結一下:TypeOf() 函數返回一個接口,這個接口定義了一系列方法,利用這些方法能夠獲取關於類型的全部信息; ValueOf() 函數返回一個結構體變量,包含類型信息以及實際值。
用一張圖來串一下:
上圖中,rtye 實現了 Type 接口,是全部類型的公共部分。emptyface 結構體和 eface 實際上是一個東西,而 rtype 其實和 _type 是一個東西,只是一些字段稍微有點差異,好比 emptyface 的 word 字段和 eface 的 data 字段名稱不一樣,可是數據型是同樣的。
反射其實主要是對接口的處理,所以必定要對接口的底層結構很是熟悉。
第一條:反射是一種檢測存儲在 interface 中的類型和值機制。這能夠經過 TypeOf 函數和 ValueOf 函數獲得。
第二條:將 ValueOf 的返回值經過 Interface() 函數反向轉變成 interface 變量。
第三條:若是須要操做一個反射變量,那麼它必須是可設置的。
複製代碼
前兩條就是說 接口型變量 和 反射類型對象 能夠相互轉化,反射類型對象實際上就是指的前面說的 reflect.Type 和 reflect.Value。
第三條不太好懂:若是須要操做一個反射變量,那麼它必須是可設置的。反射變量可設置的本質是它存儲了原變量自己,這樣對反射變量的操做,就會反映到原變量自己;反之,若是反射變量不能表明原變量,那麼操做了反射變量,不會對原變量產生任何影響,這會給使用者帶來疑惑。因此第二種狀況在語言層面是不被容許的。
舉個例子:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
複製代碼
執行上面的代碼會產生 panic,緣由是反射變量 v 不能表明 x 自己,爲何?由於調用 reflect.ValueOf(x) 這一行代碼的時候,傳入的參數在函數內部只是一個拷貝,是值傳遞,因此 v 表明的只是 x 的一個拷貝,所以對 v 進行操做是被禁止的。
可設置是反射變量 Value 的一個性質,但不是全部的 Value 都是可被設置的。
就像在通常的函數裏那樣,當咱們想改變傳入的變量時,使用指針就能夠解決了
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
結果:
type of p: *float64
settability of p: false
複製代碼
p 還不是表明 x,p.Elem() 才真正表明 x,這樣就能夠真正操做 x 了:
v := p.Elem() // 注意,前面p := reflect.ValueOf(&x)必定要傳入指針,不然這裏會panic
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1
複製代碼
關於第三條,記住一句話:若是想要操做原變量,反射變量 Value 必需要 hold 住原變量的地址才行。
利用反射機制,對於結構體中未導出成員,能夠讀取,但不能修改其值。
注意,正常狀況下,代碼是不能讀取結構體未導出成員的,但經過反射能夠越過這層限制。另外,經過反射,結構體中能夠被修改的成員只有是導出成員,也就是字段名的首字母是大寫的。
一個可取地址的 reflect.Value 變量會記錄一個結構體成員是不是未導出成員,若是是的話則拒絕修改操做。
CanAddr 不能說明一個變量是否能夠被修改。
CanSet 則能夠檢查對應的 reflect.Value 是否可取地址並可被修改。
複製代碼
【1】深度解密Go語言之反射
【2】Go 反射機制介紹