Go 反射實踐及剖析

XVMMoh 8年前發布 | 10K 次閱讀 Go語言 Google Go/Golang開發

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 &amp; flagKindMask 的意思。再 value.go 文件中找到 flagKindMask 的定義:

const (
    flagKindWidth = 5 // there are 27 kinds
    flagKindMaskflag = 1<<flagKindWidth - 1
    ...
}

所以這句 f &amp; 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/

 

 本文由用戶 XVMMoh 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!