Go是強(qiáng)類型/靜態(tài)類型語(yǔ)言,每個(gè)變量在編譯時(shí)就已經(jīng)確定是哪種靜態(tài)類型。反射(reflection)是程序在運(yùn)行時(shí)可以訪問、檢測(cè)、修改自身狀態(tài)或行為的一種能力。在Java出現(xiàn)后迅速流行起來的概念,Go也提供了這種在運(yùn)行時(shí)更新、檢查變量值、調(diào)用變量的方法和變量支持的內(nèi)在操作的機(jī)制,一定程度上彌補(bǔ)了靜態(tài)語(yǔ)言在動(dòng)態(tài)行為上的不足。
正常來講,程序在編譯時(shí)會(huì)將變量轉(zhuǎn)換為內(nèi)存地址,變量名不會(huì)被編譯器寫入可執(zhí)行部分,那么運(yùn)行時(shí)程序就無法獲取自身的信息。支持反射的語(yǔ)言則需要在程序編譯期將變量的反射信息,如字段名、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中,并給程序提供接口訪問反射信息。這樣程序運(yùn)行時(shí)即可獲取類型的反射信息,并有能力操作修改它。
反射是把雙刃劍,雖然代碼更加靈活了但是
- 代碼閱讀起來也困難了
- 一定程度上破壞了靜態(tài)類型語(yǔ)言的編譯期檢查 運(yùn)行時(shí)會(huì)有panic風(fēng)險(xiǎn)
- 降低了系統(tǒng)性能
我們?yōu)槭裁葱枰瓷洌?/h2>
- 無法預(yù)定義參數(shù)類型
- 函數(shù)需要根據(jù)入?yún)韯?dòng)態(tài)執(zhí)行
需要注意的是:Go中只有接口類型才可以反射,而反射又是建立在類型系統(tǒng)之上,so我們先來復(fù)習(xí)下類型與接口的知識(shí)
類型
Go是靜態(tài)類型語(yǔ)言。每個(gè)變量都有一個(gè)靜態(tài)類型,編譯時(shí)就已經(jīng)確定的類型:int、float32、*MyType、[]byte等等
type MyInt int
var i int
var j MyInt
上面的栗子中,i與j具有不同的靜態(tài)類型(i是int類型,j為MyInt類型),盡管他們的基礎(chǔ)類型都是int,但是他們之間不經(jīng)過轉(zhuǎn)換無法相互賦值。
類型的一個(gè)重要類別是接口類型,接口可以存儲(chǔ)任何非接口的具體值,只要該值實(shí)現(xiàn)了接口方法即可。
接口
接口是多個(gè)方法聲明的集合,側(cè)重于做什么,不關(guān)系怎么做 誰(shuí)來做。它更像是一種調(diào)用契約或協(xié)議(protocol)。接口解除了類型依賴,屏蔽了方法實(shí)現(xiàn)細(xì)節(jié),但接口的實(shí)現(xiàn)機(jī)制存在運(yùn)行時(shí)開銷。
Go的接口機(jī)制比較簡(jiǎn)潔,不像Java需要顯示聲明實(shí)現(xiàn)的接口,Go只要目標(biāo)類型方法集中包含了接口聲明的全部方法,就被稱為實(shí)現(xiàn)了該接口,無須顯示聲明。
如果一個(gè)接口沒有聲明任何方法,那么就是一個(gè)空接口interface{},類似Java的Object對(duì)象可以被賦值為任意類型的對(duì)象。但
Go語(yǔ)言的接口類型不是任意類型 只是任意類型可以通過類型轉(zhuǎn)換成接口變量
接下來我們來看看接口的數(shù)據(jù)結(jié)構(gòu),總結(jié)起來接口結(jié)構(gòu)如下:

具體可以細(xì)分為
- 不包含任何方法的接口
interface{} - 包含一組方法的接口
Go語(yǔ)言使用runtime.eface表示不包含任何方法的接口,runtime.iface表示包含一組方法的接口。
- 不包含任何方法的接口
type eface struct {
_type *_type
data unsafe.Pointer
}

- 包含一組方法的接口
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

可以看到不論空eface還是非空iface都包含了_type數(shù)據(jù)類型
type _type struct {
size uintptr //類型大小
ptrdata uintptr // 含有所有指針類型前綴大小
hash uint32 //類型hash值 避免在哈希表中計(jì)算
tflag tflag //額外類型信息標(biāo)志
align uint8 // 類型變量對(duì)齊方式
fieldalign uint8 // 類型結(jié)構(gòu)字段對(duì)齊方式
kind uint8 // 類型種類
alg *typeAlg //存儲(chǔ)hash和equal兩個(gè)操作 map的key就是適用key的_type.alg.hash(k)獲取的hash值
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff //類型名字的偏移
ptrToThis typeOff
}
當(dāng)然不同類型需要的描述是不一樣的,大多是利用_type組合其他基礎(chǔ)類型而成
接下來我們通過一個(gè)栗子拆解下接口內(nèi)存中的結(jié)構(gòu)究竟如何
type Animal interface {
Say()
}
type Dog struct {
}
func (d *Dog) Say() {
fmt.Println("wang wang")
}
//1
var animal Animal
dog := &Dog{}
//2
animal=dog
//3
var e interface{}
e = dog
- 初始化定義一個(gè)接口變量
var animal Animal

- 將實(shí)現(xiàn)接口的對(duì)象賦值給接口變量
animal=dog

- 定義一個(gè)空接口變量
var e interface{}

- 將實(shí)現(xiàn)接口的對(duì)象賦值給空接口變量
e = dog

至此,想必你應(yīng)該了解了接口的數(shù)據(jù)結(jié)構(gòu)及工作機(jī)制,接下來我們看看反射是如何工作的
反射
反射三大定律
1. Reflection goes from interface value to reflection object 接口數(shù)據(jù)-->反射對(duì)象
簡(jiǎn)單來說,反射是一種檢查存儲(chǔ)在接口變量中的類型和值的機(jī)制,reflect包定義了這兩個(gè)重要的類型Type和Value,任意接口值在反射中都可以理解為由 reflect.Type和 reflect.Value兩部分組成,可以通過reflect.TypeOf()和reflect.ValueOf()函數(shù)來獲取任意對(duì)象的Type 和Value。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

舉個(gè)栗子
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
你可能會(huì)迷惑,你不是說接口變量才支持反射的嗎?別著急 我們來仔細(xì)看看reflect.TypeOf和reflect.ValueOf是如何實(shí)現(xiàn)的
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
很簡(jiǎn)單,當(dāng)我們調(diào)用reflect.TypeOf(x)時(shí),x已經(jīng)存儲(chǔ)進(jìn)了一個(gè)空接口變量,reflect.TypeOf然后拆箱空接口變量獲取類型信息。
reflect.TypeOf和reflect.ValueOf提供了大量的方法可以讓我們檢查和操作它們。
type Type interface {
// 從內(nèi)存中申請(qǐng)一個(gè)類型值時(shí)對(duì)齊的字節(jié)數(shù).
Align() int
// 此類型作為結(jié)構(gòu)體字段時(shí)對(duì)齊的字節(jié)數(shù)
FieldAlign() int
//獲取類型的指定函數(shù)信息
Method(int) Method
//通過方法名獲取方法信息
MethodByName(string) (Method, bool)
//該類型可導(dǎo)出方法數(shù)量
NumMethod() int
// 返回包中定義類型的名稱 為定義類型返回空字符串
Name() string
// 返回類型的包路徑即唯一標(biāo)識(shí)包的路徑 如“encoding/base64”
// 預(yù)定義類型、未定義類型、[]int返回空字符串
PkgPath() string
// 返回存儲(chǔ)該類型值需要的字節(jié)數(shù) 類似unsafe.Sizeof
Size() uintptr
// 返回該類型的字符串表示形式。
String() string
// 返回類型的特定種類
Kind() Kind
// 判斷該類型是否實(shí)現(xiàn)了u類型的接口
Implements(u Type) bool
// 判斷該類型是否可賦值給u類型
AssignableTo(u Type) bool
// 判斷該類型是否可轉(zhuǎn)換為u類型
ConvertibleTo(u Type) bool
// 判斷該類型的值是否可比較
Comparable() bool
// 方法僅適用于某些類型
// 取決于具體類型
// Int*, Uint*, Float*, Complex*: Bits
// Array: Elem, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic.
// Map: Key, Elem
// Ptr: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
// 返回類型占用的bit值
//非 sized or unsized Int, Uint, Float, or Complex 會(huì)panic
Bits() int
// 返回channel類型 非chan類型panic
ChanDir() ChanDir
// 判斷函數(shù)是否有可變參數(shù) 非函數(shù)類型會(huì)panic
IsVariadic() bool
// 返回元素類型
// 非 Array, Chan, Map, Ptr, or Slice會(huì)panic
Elem() Type
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
// 返回結(jié)構(gòu)體類型第i個(gè)字段
Field(i int) StructField
// 等價(jià)于Field(i)
// It panics if the type's Kind is not Struct.
FieldByIndex(index []int) StructField
// 根據(jù)名字返回字段信息
// and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)
//利用函數(shù)查找字段名符合條件的字段信息 使用廣度優(yōu)先的策略 如果發(fā)現(xiàn)多個(gè)匹配則不返回匹配項(xiàng)
FieldByNameFunc(match func(string) bool) (StructField, bool)
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
// 返回函數(shù)第i個(gè)入?yún)? In(i int) Type
// It panics if the type's Kind is not Map.
// 返回map的key類型
Key() Type
// It panics if the type's Kind is not Array.
// 返回?cái)?shù)組類型的長(zhǎng)度
Len() int
// It panics if the type's Kind is not Struct.
// 返回結(jié)構(gòu)體類型字段數(shù)量
NumField() int
// It panics if the type's Kind is not Func.
// 返回函數(shù)類型入?yún)?shù)量
NumIn() int
// It panics if the type's Kind is not Func.
// 返回函數(shù)類型出參數(shù)量
NumOut() int
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
// 返回函數(shù)類型第i個(gè)出參
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
Value
type Value struct {
// typ 包含由Value表示值的類型
typ *rtype
// 指針值數(shù)據(jù),如果設(shè)置flagIndir則指向數(shù)據(jù)
// 當(dāng)設(shè)置flagIndir或typ.pointers()為true時(shí)有效
ptr unsafe.Pointer
// flag保存有關(guān)值的元數(shù)據(jù)
// 最低位是flag標(biāo)志位:
// - flagStickyRO: 通過未導(dǎo)出未嵌入的字段獲取 故只讀
// - flagEmbedRO: 通過未導(dǎo)出嵌入字段獲取故只讀
// - flagIndir: val保存指向數(shù)據(jù)的指針
// - flagAddr: v.CanAddr 為 true (表示 flagIndir)
// - flagMethod: v 為方法值
// 接下來的5位給出值的類型
// 重復(fù)typ.Kind() 方法值除外.
// 剩余23+位給方法值的方法編號(hào)
// 如果flag.kind() != Func, 代碼可假定未設(shè)置flagMethod
// 如果ifaceIndir(typ), 代碼可假設(shè)設(shè)置了flagIndir
flag
}
2. Reflection goes from reflection object to interface value 反射對(duì)象 -->接口數(shù)據(jù)
像物理反射一樣,Go的反射也會(huì)生成自己的逆。給出一個(gè)reflect.Value我們可以使用Interface()方法獲取接口的值。實(shí)際上就是將該類型和值信息打包成接口表示形式并返回。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
例如:
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

當(dāng)然reflect.Value通過Value.Type()也可以直接獲取reflect.Type

3. To modify a reflection object,the value must be settable 若數(shù)據(jù)可修改 可通過反射對(duì)象來修改它
我們先來看個(gè)栗子:
var a float64
fmt.Println(a)
va := reflect.ValueOf(a)
va.SetFloat(11)
fmt.Println(a)
輸出:
panic: using unaddressable value
為何?看似操作沒問題。其實(shí)仔細(xì)想想,Go是值傳遞va := reflect.ValueOf(a)中我們相當(dāng)于傳遞了a的拷貝給了reflect.ValueOf,因此即使va.SetFloat(11)修改成功了也無法到達(dá)修改a原始值的目的,故而利用這種Type是否CanSet來避免這種問題。正確做法
- 首先根據(jù)變量地址獲取
reflect.Value即va := reflect.ValueOf(&a) -
va.SetFloat(11)此時(shí)依然無法成功 因?yàn)榇藭r(shí)的va仍然是一個(gè)拷貝值,如若修改需要使用va.Elem()獲取*va
var a float64
fmt.Println(a)
va := reflect.ValueOf(&a)
va.Elem().SetFloat(11)
fmt.Println(a) // 11
反射的應(yīng)用
反射廣泛應(yīng)用在對(duì)象序列化,fmt相關(guān)的函數(shù)以及ORM(Object Relational Mapping)等等
例如:JSON序列化
Go內(nèi)置的Json序列化提供了兩個(gè)方法
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
序列化和反序列化參數(shù)中都有interface{}類型的變量,所以當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí)需要使用reflect包中的方法后期參數(shù)的reflect.Value和reflect.Type,進(jìn)而調(diào)用其get、set方法。
序列化
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
......
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}

總結(jié)
Go作為靜態(tài)語(yǔ)言,相對(duì)于動(dòng)態(tài)語(yǔ)言,在靈活性上受到某些限制。但是通過reflect包提供類似動(dòng)態(tài)語(yǔ)言的功能,你可以運(yùn)行時(shí)獲取參數(shù)的Value和Type進(jìn)而完成一些特定的需求。其轉(zhuǎn)換關(guān)系如圖
