Go是強類型/靜態(tài)類型語言筹误,每個變量在編譯時就已經(jīng)確定是哪種靜態(tài)類型蟹但。反射(reflection
)是程序在運行時可以訪問、檢測拂苹、修改自身狀態(tài)或行為的一種能力安聘。在Java
出現(xiàn)后迅速流行起來的概念,Go也提供了這種在運行時更新、檢查變量值浴韭、調(diào)用變量的方法和變量支持的內(nèi)在操作的機制,一定程度上彌補了靜態(tài)語言在動態(tài)行為上的不足丘喻。
正常來講,程序在編譯時會將變量轉(zhuǎn)換為內(nèi)存地址念颈,變量名不會被編譯器寫入可執(zhí)行部分泉粉,那么運行時程序就無法獲取自身的信息。支持反射的語言則需要在程序編譯期將變量的反射信息榴芳,如字段名嗡靡、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中窟感,并給程序提供接口訪問反射信息讨彼。這樣程序運行時即可獲取類型的反射信息,并有能力操作修改它柿祈。
反射是把雙刃劍哈误,雖然代碼更加靈活了但是
- 代碼閱讀起來也困難了
- 一定程度上破壞了靜態(tài)類型語言的編譯期檢查 運行時會有panic風險
- 降低了系統(tǒng)性能
我們?yōu)槭裁葱枰瓷洌?/h2>
- 無法預定義參數(shù)類型
- 函數(shù)需要根據(jù)入?yún)韯討B(tài)執(zhí)行
需要注意的是:Go中只有接口類型才可以反射,而反射又是建立在類型系統(tǒng)之上谍夭,so我們先來復習下類型與接口的知識
類型
Go是靜態(tài)類型語言黑滴。每個變量都有一個靜態(tài)類型,編譯時就已經(jīng)確定的類型:int紧索、float32袁辈、*MyType、[]byte等等
type MyInt int
var i int
var j MyInt
上面的栗子中珠漂,i與j具有不同的靜態(tài)類型(i是int類型晚缩,j為MyInt類型),盡管他們的基礎類型都是int媳危,但是他們之間不經(jīng)過轉(zhuǎn)換無法相互賦值荞彼。
類型的一個重要類別是接口類型,接口可以存儲任何非接口的具體值待笑,只要該值實現(xiàn)了接口方法即可鸣皂。
接口
接口是多個方法聲明的集合,側(cè)重于做什么暮蹂,不關(guān)系怎么做 誰來做寞缝。它更像是一種調(diào)用契約或協(xié)議(protocol)。接口解除了類型依賴仰泻,屏蔽了方法實現(xiàn)細節(jié)荆陆,但接口的實現(xiàn)機制存在運行時開銷。
Go
的接口機制比較簡潔集侯,不像Java
需要顯示聲明實現(xiàn)的接口被啼,Go
只要目標類型方法集中包含了接口聲明的全部方法帜消,就被稱為實現(xiàn)了該接口,無須顯示聲明浓体。
如果一個接口沒有聲明任何方法泡挺,那么就是一個空接口interface{}
,類似Java
的Object
對象可以被賦值為任意類型的對象命浴。但
Go語言的接口類型不是任意類型 只是任意類型可以通過類型轉(zhuǎn)換成接口變量
接下來我們來看看接口的數(shù)據(jù)結(jié)構(gòu)粘衬,總結(jié)起來接口結(jié)構(gòu)如下:
具體可以細分為
- 不包含任何方法的接口
interface{}
- 包含一組方法的接口
Go語言使用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值 避免在哈希表中計算
tflag tflag //額外類型信息標志
align uint8 // 類型變量對齊方式
fieldalign uint8 // 類型結(jié)構(gòu)字段對齊方式
kind uint8 // 類型種類
alg *typeAlg //存儲hash和equal兩個操作 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
}
當然不同類型需要的描述是不一樣的,大多是利用_type
組合其他基礎類型而成
接下來我們通過一個栗子拆解下接口內(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
- 初始化定義一個接口變量
var animal Animal
- 將實現(xiàn)接口的對象賦值給接口變量
animal=dog
- 定義一個空接口變量
var e interface{}
- 將實現(xiàn)接口的對象賦值給空接口變量
e = dog
至此勘伺,想必你應該了解了接口的數(shù)據(jù)結(jié)構(gòu)及工作機制跪腹,接下來我們看看反射是如何工作的
反射
反射三大定律
1. Reflection goes from interface value to reflection object 接口數(shù)據(jù)-->反射對象
簡單來說,反射是一種檢查存儲在接口變量中的類型和值的機制飞醉,reflect
包定義了這兩個重要的類型Type
和Value
冲茸,任意接口值在反射中都可以理解為由 reflect.Type
和 reflect.Value
兩部分組成,可以通過reflect.TypeOf()
和reflect.ValueOf()
函數(shù)來獲取任意對象的Type
和Value
缅帘。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
舉個栗子
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
你可能會迷惑,你不是說接口變量才支持反射的嗎轴术?別著急 我們來仔細看看reflect.TypeOf
和reflect.ValueOf
是如何實現(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)
}
很簡單,當我們調(diào)用reflect.TypeOf(x)
時钦无,x
已經(jīng)存儲進了一個空接口變量逗栽,reflect.TypeOf
然后拆箱空接口變量獲取類型信息。
reflect.TypeOf
和reflect.ValueOf
提供了大量的方法可以讓我們檢查和操作它們失暂。
type Type interface {
// 從內(nèi)存中申請一個類型值時對齊的字節(jié)數(shù).
Align() int
// 此類型作為結(jié)構(gòu)體字段時對齊的字節(jié)數(shù)
FieldAlign() int
//獲取類型的指定函數(shù)信息
Method(int) Method
//通過方法名獲取方法信息
MethodByName(string) (Method, bool)
//該類型可導出方法數(shù)量
NumMethod() int
// 返回包中定義類型的名稱 為定義類型返回空字符串
Name() string
// 返回類型的包路徑即唯一標識包的路徑 如“encoding/base64”
// 預定義類型彼宠、未定義類型、[]int返回空字符串
PkgPath() string
// 返回存儲該類型值需要的字節(jié)數(shù) 類似unsafe.Sizeof
Size() uintptr
// 返回該類型的字符串表示形式弟塞。
String() string
// 返回類型的特定種類
Kind() Kind
// 判斷該類型是否實現(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 會panic
Bits() int
// 返回channel類型 非chan類型panic
ChanDir() ChanDir
// 判斷函數(shù)是否有可變參數(shù) 非函數(shù)類型會panic
IsVariadic() bool
// 返回元素類型
// 非 Array, Chan, Map, Ptr, or Slice會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個字段
Field(i int) StructField
// 等價于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)多個匹配則不返回匹配項
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個入?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.
// 返回數(shù)組類型的長度
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個出參
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
Value
type Value struct {
// typ 包含由Value表示值的類型
typ *rtype
// 指針值數(shù)據(jù)凭峡,如果設置flagIndir則指向數(shù)據(jù)
// 當設置flagIndir或typ.pointers()為true時有效
ptr unsafe.Pointer
// flag保存有關(guān)值的元數(shù)據(jù)
// 最低位是flag標志位:
// - flagStickyRO: 通過未導出未嵌入的字段獲取 故只讀
// - flagEmbedRO: 通過未導出嵌入字段獲取故只讀
// - flagIndir: val保存指向數(shù)據(jù)的指針
// - flagAddr: v.CanAddr 為 true (表示 flagIndir)
// - flagMethod: v 為方法值
// 接下來的5位給出值的類型
// 重復typ.Kind() 方法值除外.
// 剩余23+位給方法值的方法編號
// 如果flag.kind() != Func, 代碼可假定未設置flagMethod
// 如果ifaceIndir(typ), 代碼可假設設置了flagIndir
flag
}
2. Reflection goes from reflection object to interface value 反射對象 -->接口數(shù)據(jù)
像物理反射一樣,Go的反射也會生成自己的逆决记。給出一個reflect.Value
我們可以使用Interface()
方法獲取接口的值摧冀。實際上就是將該類型和值信息打包成接口表示形式并返回。
// 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)
當然reflect.Value
通過Value.Type()
也可以直接獲取reflect.Type
3. To modify a reflection object,the value must be settable 若數(shù)據(jù)可修改 可通過反射對象來修改它
我們先來看個栗子:
var a float64
fmt.Println(a)
va := reflect.ValueOf(a)
va.SetFloat(11)
fmt.Println(a)
輸出:
panic: using unaddressable value
為何系宫?看似操作沒問題索昂。其實仔細想想,Go是值傳遞va := reflect.ValueOf(a)
中我們相當于傳遞了a
的拷貝給了reflect.ValueOf
笙瑟,因此即使va.SetFloat(11)
修改成功了也無法到達修改a
原始值的目的楼镐,故而利用這種Type是否CanSet
來避免這種問題。正確做法
- 首先根據(jù)變量地址獲取
reflect.Value
即va := reflect.ValueOf(&a)
-
va.SetFloat(11)
此時依然無法成功 因為此時的va
仍然是一個拷貝值往枷,如若修改需要使用va.Elem()
獲取*va
var a float64
fmt.Println(a)
va := reflect.ValueOf(&a)
va.Elem().SetFloat(11)
fmt.Println(a) // 11
反射的應用
反射廣泛應用在對象序列化框产,fmt
相關(guān)的函數(shù)以及ORM(Object Relational Mapping)等等
例如:JSON序列化
Go內(nèi)置的Json序列化提供了兩個方法
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
序列化和反序列化參數(shù)中都有interface{}
類型的變量凄杯,所以當我們調(diào)用這個函數(shù)時需要使用reflect
包中的方法后期參數(shù)的reflect.Value
和reflect.Type
,進而調(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)語言,相對于動態(tài)語言描睦,在靈活性上受到某些限制膊存。但是通過reflect
包提供類似動態(tài)語言的功能,你可以運行時獲取參數(shù)的Value
和Type
進而完成一些特定的需求忱叭。其轉(zhuǎn)換關(guān)系如圖