Go 反射實踐及剖析
Go struct拷貝
在用Go做orm相關操作的時候,經常會有struct之間的拷貝。比如下面兩個struct之間要拷貝共同成員B,C。這個在struct不是很大的時候從來都不是問題,直接成員拷貝即可。但是當struct的大小達到三四十個成員的時候,就要另辟蹊徑了。
type A struct {
A int
B int
C string
E string
}
type B struct {
B int
C string
D int
E string
}
做法一. 反射
funcCopyStruct(src, dst interface{}) {
sval := reflect.ValueOf(src).Elem()
dval := reflect.ValueOf(dst).Elem()
for i := 0; i < sval.NumField(); i++ {
value := sval.Field(i)
name := sval.Type().Field(i).Name
dvalue := dval.FieldByName(name)
if dvalue.IsValid() == false {
continue
}
dvalue.Set(value)
}
}
做法二. json
先encode成json,再decode,其實golang的json包內部實現也是使用的反射,所以再大型項目中可以考慮使用ffjson來作為替代方案。
funcmain() {
a := &A{1, "a", 1}
// b := &B{"b",2,2}
aj, _ := json.Marshal(a)
b := new(B)
_ = json.Unmarshal(aj, b)
fmt.Printf("%+v", b)
}
關于golang中的反射機制一直是大家詬病挺多的。因為反射中使用了類型的枚舉,所以效率比較低,在高性能場景中應該盡量規避,但是,對于大部分應用場景來說,犧牲一點性能來極大減少代碼行數,或者說提高開發效率都是值得的。
Reflect
關于Reflect的接口可以參考golang的文檔,也可以直接看go的源碼。reflect的核心是兩個,一個是Type,一個是Value。reflect的使用一般都是以下面語句開始。
Value
Value的定義很簡單,如下。
type Value struct {
typ *rtype
ptrunsafe.Pointer //pointer-valued data or pointer to data
flag //metedata
}
下面針對object的類型不同來看一下reflect提供的方法。
built-in type
reflect針對基本類型提供了如下方法,以Float()為例展開。
//讀
func (v Value) Float() float64 {
k := v.kind()
switch k {
case Float32:
return float64(*(*float32)(v.ptr))
case Float64:
return *(*float64)(v.ptr)
}
panic(&ValueError{"reflect.Value.Float", v.kind()})
}
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) String() string
func (v Value) Complex() complex128
//寫
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64) {
v.mustBeAssignable()
switch k := v.kind(); k {
default:
panic(&ValueError{"reflect.Value.SetInt", v.kind()})
case Int:
*(*int)(v.ptr) = int(x)
case Int8:
*(*int8)(v.ptr) = int8(x)
case Int16:
*(*int16)(v.ptr) = int16(x)
case Int32:
*(*int32)(v.ptr) = int32(x)
case Int64:
*(*int64)(v.ptr) = x
}
}
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, valValue)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
可以看到內部Float內部實現先通過kind()判斷value的類型,然后通過指針取數據并做類型轉換。kind()的定義如下
func (f flag) kind() Kind {
return Kind(f & flagKindMask)
}
flag是Value的內部成員,所以方法也被繼承過來。通過實現不難猜到reflect對不同的類型是通過一個整數來實現的。我們來驗證一下,在type.go文件中找到Kind定義,注意這個地方Kind()只是一個類型轉換。
typeKinduint
const (
InvalidKind = iota
Bool
Int
Int8
Int16
Int32
...
)
緊挨著Kind定義的下面就是各種類型的表示:Bool=1, Int=2,…再來看一下 f & flagKindMask 的意思。再 value.go 文件中找到 flagKindMask 的定義:
const (
flagKindWidth = 5 // there are 27 kinds
flagKindMaskflag = 1<<flagKindWidth - 1
...
}
所以這句 f & flagKindMask 的意思就是最f的低5位,也就是對應了上面的各種類型。
上面說完了數據的讀接口,其實寫接口也很類似。唯一的區別在于reflect還提供了一個Set()方法,就是說我們自己去保證數據的類型正確性。
Struct
其實使用反射的很多場景都是struct。reflect針對struct提供的函數方法如下:
func (v Value) Elem() Value //返回指針或者interface包含的值
func (v Value) Field(i int) Value //返回struct的第i個field
func (v Value) FieldByIndex(index []int) Value //返回嵌套struct的成員
func (v Value) FieldByName(namestring) Value //通過成員名稱返回對應的成員
func (v Value) FieldByNameFunc(matchfunc(string) bool) Value //只返回滿足函數match的第一個field
通過上面的方法不出意外就可以取得對應是struct field了。
其他類型:Array, Slice, String
對于其他類型,reflect也提供了獲得其內部成員的方法。
func (v Value) Len() int //Array, Chan, Map, Slice, or String
func (v Value) Index(i int) Value //Array, Slice, String
func (v Value) Cap() int //Array, Chan, Slice
func (v Value) Close() //Chan
func (v Value) MapIndex(keyValue) Value //Map
func (v Value) MapKeys() []Value //Map
函數調用
reflect當然也可以實現函數調用,下面是一個簡單的例子。
funcmain() {
var f = func() {
fmt.Println("hello world")
}
fun := reflect.ValueOf(f)
fun.Call(nil)
}
//Output
helloworld
當然我們還可以通過struct來調用其方法,需要注意的一點是通過反射調用的函數必須是外部可見的(首字母大寫)。
type S struct {
A int
}
func (s S) Method1() {
fmt.Println("method 1")
}
func (s S) Method2() {
fmt.Println("method 2")
}
funcmain() {
var a S
S := reflect.ValueOf(a)
fmt.Println(S.NumMethod())
m1 :=S.Method(0)
m1.Call(nil)
m2 := S.MethodByName("Method2")
m2.Call(nil)
}
總結一下reflect提供了函數調用的相關接口如下:
func (v Value) Call(in []Value) []Value
func (v Value) CallSlice(in []Value) []Value
func (v Value) Method(i int) Value //v's ith function
func (v Value) NumMethod() int
func (v Value) MethodByName(namestring) Value
Type
Type是反射中另外重要的一部分。Type是一個接口,里面包含很多方法,通常我們可以通過 reflect.TypeOf(obj) 取得obj的類型對應的Type接口。接口中很多方法都是對應特定類型的,所以調用的時候需要注意。
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
...
另外reflect包還為我們提供了幾個生成特定類型的Type接口的方法。這里就不一一列舉了。
小結
關于reflect提供的接口需要注意的一點就是,一定要保證類型是匹配的,如果不匹配將導致panic。關于Value的主要接口都在這,本來還想寫一下Type以及內部的實現機制的,只能放到下篇再寫了。
來自:http://blog.jobbole.com/108601/