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
.
.
.
)
|
紧挨着Kind定义的下面就是各种类型的表示:Bool=1, Int=2,…再来看一下f & flagKindMask
的意思。再value.go
文件中找到flagKindMask
的定义:
1
2
3
4
5
|
const
(
flagKindWidth
=
5
// there are 27 kinds
flagKindMask
flag
=
1
<<
flagKindWidth
-
1
.
.
.
}
|
所以这句f & flagKindMask
的意思就是最f的低5位,也就是对应了上面的各种类型。
上面说完了数据的读接口,其实写接口也很类似。唯一的区别在于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
Type是反射中另外重要的一部分。Type是一个接口,里面包含很多方法,通常我们可以通过reflect.TypeOf(obj)
取得obj的类型对应的Type接口。接口中很多方法都是对应特定类型的,所以调用的时候需要注意。
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接口的方法。这里就不一一列举了。