Go struct拷贝

在用Go做orm相关操作的时候,经常会有struct之间的拷贝。比如下面两个struct之间要拷贝共同成员B,C。这个在struct不是很大的时候从来都不是问题,直接成员拷贝即可。但是当struct的大小达到三四十个成员的时候,就要另辟蹊径了。

1
2
3
4
5
6
7
8
9
10
11
12
13
type A struct {
     A int
     B int
     C string
     E string
}
 
type B struct {
     B int
     C string
     D int
     E string
}

做法一. 反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func CopyStruct ( 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来作为替代方案。

1
2
3
4
5
6
7
8
9
10
func main ( ) {
     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的定义很简单,如下。

1
2
3
4
5
type Value struct {
     typ * rtype
     ptr unsafe . Pointer //pointer-valued data or pointer to data
     flag //metedata
}

下面针对object的类型不同来看一下reflect提供的方法。

built-in type

reflect针对基本类型提供了如下方法,以Float()为例展开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//读
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 , val Value )
func ( v Value ) SetPointer ( x unsafe . Pointer )
func ( v Value ) SetString ( x string )
func ( v Value ) SetUint ( x uint64 )

可以看到内部Float内部实现先通过kind()判断value的类型,然后通过指针取数据并做类型转换。kind()的定义如下

1
2
3
func ( f flag ) kind ( ) Kind {
return Kind ( f & flagKindMask )
}

flag是Value的内部成员,所以方法也被继承过来。通过实现不难猜到reflect对不同的类型是通过一个整数来实现的。我们来验证一下,在type.go文件中找到Kind定义,注意这个地方Kind()只是一个类型转换。

1
2
3
4
5
6
7
8
9
10
11
type Kind uint
 
const (
     Invalid Kind = iota
     Bool
     Int
     Int8
     Int16
     Int32
     . . .
)
f & flagKindMaskvalue.goflagKindMask
1
2
3
4
5
const (
     flagKindWidth = 5 // there are 27 kinds
     flagKindMask flag = 1 << flagKindWidth - 1
     . . .
}
f & flagKindMask

上面说完了数据的读接口,其实写接口也很类似。唯一的区别在于reflect还提供了一个Set()方法,就是说我们自己去保证数据的类型正确性。

Struct

其实使用反射的很多场景都是struct。reflect针对struct提供的函数方法如下:

1
2
3
4
5
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 ( name string ) Value //通过成员名称返回对应的成员
func ( v Value ) FieldByNameFunc ( match func ( string ) bool ) Value //只返回满足函数match的第一个field

通过上面的方法不出意外就可以取得对应是struct field了。

其他类型:Array, Slice, String

对于其他类型,reflect也提供了获得其内部成员的方法。

1
2
3
4
5
6
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 ( key Value ) Value //Map
func ( v Value ) MapKeys ( ) [ ] Value //Map

函数调用

reflect当然也可以实现函数调用,下面是一个简单的例子。

1
2
3
4
5
6
7
8
9
10
func main ( ) {
     var f = func ( ) {
         fmt . Println ( "hello world" )
     }
 
     fun : = reflect . ValueOf ( f )
     fun . Call ( nil )
}
//Output
hello world

当然我们还可以通过struct来调用其方法,需要注意的一点是通过反射调用的函数必须是外部可见的(首字母大写)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type S struct {
     A int
}
 
func ( s S ) Method1 ( ) {
     fmt . Println ( "method 1" )
}
 
func ( s S ) Method2 ( ) {
     fmt . Println ( "method 2" )
}
 
func main ( ) {
     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提供了函数调用的相关接口如下:

1
2
3
4
5
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 ( name string ) Value

Type

reflect.TypeOf(obj)
1
2
3
4
5
6
7
type Type interface {
     Align ( ) int
     FieldAlign ( ) int
     Method ( int ) Method
     MethodByName ( string ) ( Method , bool )
     NumMethod ( ) int
     . . .

另外reflect包还为我们提供了几个生成特定类型的Type接口的方法。这里就不一一列举了。