1、接口
1.1 类型
Golang
中的接口是一组方法的签名,是实现多态和反射的基础。
type 接口名 interface {
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
}
不同于 Java
语言,使用 implements
关键字显示的实现接口。Golang
接口的实现都是隐式的,只需要实现了接口类型中的所有方法就实现了接口。
func (t 自定义类型) method1(参数列表) (返回值列表) {
//方法实现
}
func (t 自定义类型) method2(参数列表) (返回值列表) {
//方法实现
}
这里来看个示例
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d *Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c *Cat) Speak() string {
return "Meow!"
}
func PrintSpeak(s Speaker) {
fmt.Println(s.Speak())
}
func TestSpeaker(t *testing.T) {
dog := &Dog{}
cat := &Cat{}
PrintSpeak(dog)
PrintSpeak(cat)
}
在这个例子中,Dog
和 Cat
类型都实现了 Speaker
接口的 Speak
方法。PrintSpeak
函数接受一个 Speaker
接口类型的参数,因此可以接受任何实现了 Speaker
接口的类型。这展示了多态性的强大之处:不同的类型可以通过相同的接口进行交互。
这里可以再展开下,利用接口多态的特点,服务可以对外提供一个元数据接口,依据传参的不同,返回不同的数据模板。比如,type
字段来做区分,rider
代表返回骑手的相关信息,order
代表返回订单的相关信息,delivery
代表返回运单的相关。服务内部就可以依据 type
的不同,来分别封装实现。
另外,Golang
接口不能包含任何变量,且允许为空。空接口 interface{}
没有任何方法,所以所有类型都实现了空接口。
func TestGeneric(t *testing.T) {
var values []interface{}
values = append(values, 42)
values = append(values, "hello")
values = append(values, 3.14)
for _, value := range values {
fmt.Println(value)
}
}
上述代码展示了如何使用空接口 (interface{}
) 来存储不同类型的值,并通过循环遍历这些值进行打印,仔细看是不是像泛型?
因为接口在定义一组方法时没有对实现的接收者做限制
,所以在这一节的最后,来探讨下结构体实现接口和结构体指针实现接口的不同。
- 示例一
type Animal interface {
Say()
}
type Cat struct{}
// 接收者类型为结构体指针
func (c *Cat) Say() {
fmt.Println("miu")
}
func TestStruct(t *testing.T) {
var animal Animal = &Cat{}
animal.Say()
}
程序正常运行。
- 示例二
type Animal interface {
Say()
}
type Cat struct{}
// 接收者类型为结构体
func (c Cat) Say() {
fmt.Println("miu")
}
func TestStruct(t *testing.T) {
var animal Animal = &Cat{}
animal.Say()
}
程序正常运行。
- 示例三
type Animal interface {
Say()
}
type Cat struct{}
// 接收者类型为结构体
func (c Cat) Say() {
fmt.Println("miu")
}
func TestStruct(t *testing.T) {
var animal Animal = Cat{}
animal.Say()
}
程序正常运行。
- 示例四
type Animal interface {
Say()
}
type Cat struct{}
// 接收者类型为结构体指针
func (c *Cat) Say() {
fmt.Println("miu")
}
func TestStruct(t *testing.T) {
var animal Animal = Cat{}
animal.Say()
}
运行失败,输出
cannot use Cat{} (value of type Cat) as type Animal in variable declaration:
Cat does not implement Animal (Say method has pointer receiver)
编译器提示 Cat
没有实现 Animal
接口,Say
方法接受的是指针。
针对上述四个示例汇总如下
结构体实现接口 | 结构体指针实现接口 | |
---|---|---|
结构体初始化变量 | 通过 | 不通过 |
结构体指针初始化变量 | 通过 | 通过 |
为什么会出现这种情况呢?我们知道 Golang
中传递参数都是值传递
- 对于
&Cat{}
来说,这意味着拷贝一个新的&Cat{}
指针,不过这个指针与原来的指针指向一个相同并且唯一的结构体,所以编译器可以隐式的对变量解引用(dereference
)获取指针指向的结构体 - 而对于
Cat{}
来说,这意味着Say
方法会接受一个全新的Cat{}
,因为方法的参数是*Cat
,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体;
这里可以看出,当接受着为结构体时,那么在方法调用的时候需要传值,拷贝参数,这里会有性能损失,因此建议在实际项目中,接受者使用结构体指针来实现。
1.2 数据结构
golang
版本1.19.12
Golang
中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的接口,下面就分别介绍下其底层实现。
1.2.1 空接口
空接口的实现如下
type EmptyInterface {}
其底层的数据结构如下
// runtime/runtime2.go
type iface struct {
_type *_type
data unsafe.Pointer
}
eface
的结构体由两个属性构成,一个是类型信息 _type
,一个是数据信息 data
,占 16
个字节。
_type
属性,存放的是类型、方法等信息。
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {
size uintptr // 类型占用内存大小
ptrdata uintptr // 包含所有指针的内存前缀大小
hash uint32 // 类型 hash,用于比较两个类型是否相等
tflag tflag // 标记位,主要用于反射
align uint8 // 对齐字节信息
fieldAlign uint8 // 当前结构字段的对齐字节数
kind uint8 // 基础类型枚举值
equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等
gcdata *byte // GC 类型的数据
str nameOff // 类型名称字符串在二进制文件段中的偏移量
ptrToThis typeOff // 类型元信息指针在二进制文件段中的偏移量
}
其中可以关注下 kind
,这个字段描述的是如何解析基础类型。在 Golang
中,基础类型是一个枚举常量,有 26
个基础类型,如下。枚举值通过 kindMask
取出特殊标记位。
// runtime/typekind.go
const (
kindBool = 1 + iota
kindInt
kindInt8
kindInt16
kindInt32
kindInt64
kindUint
kindUint8
kindUint16
kindUint32
kindUint64
kindUintptr
kindFloat32
kindFloat64
kindComplex64
kindComplex128
kindArray
kindChan
kindFunc
kindInterface
kindMap
kindPtr
kindSlice
kindString
kindStruct
kindUnsafePointer
kindDirectIface = 1 << 5
kindGCProg = 1 << 6
kindMask = (1 << 5) - 1
)
这里再做个简单的展开, kindMask
的值为 31
,对应的二进制为 00011111
,也就是低五位都为 1
,再看下 Golang
中有 26
个基础类型,也就是都比 kindMask
值要小,这时利用位与运算的特性(如果两个对应位都是 1,则结果位为 1,否则为 0
),二者做与运算可以获取对应类型的种类信息。
data
属性,指向原始数据的指针,是一个unsafe.Pointer
类型
下面用个示例演示空接口 eface
数据到底是如何存储的。
type nameOff int32 // offset to a name
type typeOff int32 // offset to an *rtype
type tflag uint8
type eface struct {
_type *_type
data unsafe.Pointer
}
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// 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
}
func TestEFace(t *testing.T) {
var i interface{} = 3.14
e := (*eface)(unsafe.Pointer(&i))
fmt.Println(e)
fmt.Println(e._type)
fmt.Println(e.data)
}
打印输出一目了然。
&{0x1049dc640 0x1049ce618}
&{8 0 2472095124 7 8 8 14 0x1048bafd0 0x1049ce678 7052 34048}
0x1049ce618
从上述 eface
结构的两个属性可以推断出,Golang
的任意类型都可以转换成 interface{}
。
1.2.2 非空接口
空接口的实现如下
type NoEmptyInterface {
Say()
}
其底层的数据结构如下
type iface struct {
tab *itab
data unsafe.Pointer
}
iface
结构体也是占 16
个字节,这里的 data
属性和空接口的 eface
里的 data
作用相同,这里就不做赘述。下面来看看 tab
属性。
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.
type itab struct {
inter *interfacetype // 存的是 interface 自己的静态类型
_type *_type // 存的是 interface 对应具体对象的类型
hash uint32 // 是对 _type.hash 的拷贝
_ [4]byte
fun [1]uintptr // 是一个函数指针,它指向的是具体类型的函数方法
}
itab
结构体是非空接口的核心组成部分,占 32
字节,着重看下 inter
和 _type
属性。
type imethod struct {
name nameOff
ityp typeOff
}
type interfacetype struct {
typ _type // 类型元信息
pkgpath name // 包路径和描述信息等等
mhdr []imethod // 方法
}
inter
存储的是非空接口自己类型相关数据,因为 Golang
中函数方法是以包为单位隔离的。所以 interfacetype
除了保存 _type
还需要保存包路径等描述信息。mhdr
存的是各个 interface
函数方法在段内的偏移值 offset
,知道偏移值以后才方便调用。
_type
在上一节空接口已详细介绍过,存储的是接口类型的元信息,这里就不展开。
这里还要说下 tab
中 fun
属性,存储的是指向实现非空接口类型的方法数组。
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.
}
type imethod struct {
name nameOff
ityp typeOff
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
type name struct {
bytes *byte
}
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// 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 People interface{
Say()
}
type Student struct {}
func (s *Student) Say() {
fmt.Println("hello world")
}
func TestNoEmptyInterface(t *testing.T) {
s := &Student{}
var i People = s
noEmpty := (*iface)(unsafe.Pointer(&i))
fmt.Println(noEmpty)
fmt.Println(noEmpty.tab)
fmt.Println(noEmpty.tab.inter)
fmt.Println(noEmpty.tab._type)
fmt.Println(noEmpty.tab.fun)
fmt.Println(noEmpty.data)
}
打开 Goland
的调试模式,可以很清晰的看到非空接口内部字段都是如何存储的。
type Student struct{}
func (stu *Student) Show() {}
func live() People {
var stu *Student
return stu
}
func TestNil(t *testing.T) {
stu := live()
if stu == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
}
看完空接口这一节,上面输出应该就简单了吧,输出 not nil
。至于原因嘛,在运行时,非空接口只是 data
为 nil
,但是 tab
可不为 nil
。
这里再做个展开,简单说说动态派发。
Go
中 interface
可以动态派发方法,实现类似面向对象语言中的多态的特性。
func TestDynamic(t *testing.T) {
var stu People = &Student{}
stu.Show()
}
这里为了正常实现 stu.Show()
方法调用,需要构建 iface
结构,之后再依据 *tab
里面存的 fun
指针做一次寻址,接着才能调用。
func TestDynamic(t *testing.T) {
var stu := &Student{}
stu.Show()
}
这里直接调用结构体的方法,少了构建 iface
结构以及寻址的时间,性能应该比动态派发要好。不过,指针实现的动态派发造成的性能损失非常小,相对于一些复杂逻辑的处理函数,这点性能损失几乎可以忽略不计。
2、反射
Golang
中的反射是用标准库中的 reflect
包实现,reflect
包实现了 runtime
(运行时)的反射能力,能够让程序操作不同的对象。
reflect
包中有两个非常重要的函数:
reflect.TypeOf
能获取类型信息;reflect.ValueOf
能获取数据的运行时表示;
2.1 TypeOf
先来看看 TypeOf
函数
// reflect/type.go
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
其中入参 i
的类型 any
就是 interface{}
的别名,any
在 Golang 1.18
中引入,表示任意类型。
type any = interface{}
TypeOf
函数实现很简单,只是将一个 interface{}
变量转换成了内部的 emptyInterface
表示,然后从中获取相应的类型信息。里面有个强制转换,把 any
转成了 emptyInterface
。下面来看看 emptyInterface
的数据结构:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
仔细看看 emptyInterface
结构,发现其属性和上一章节的空接口是一模一样的,只是名称改了下, rtype
用于表示变量的类型, word
指向内部封装的数据。
从 TypeOf
参数 i any
中可以看出,入参是两种类型
- 一种是具体类型变量,
TypeOf()
返回的具体类型信息 - 一种是
interface
类型变量- 如果
i
绑定了具体类型对象实例,返回的是i
绑定具体类型的动态类型信息; - 如果
i
没有绑定任何具体的类型对象实例,返回的是接口自身的静态类型信息。
- 如果
type Forest interface{}
type Tree struct{}
func TestBasicType(t *testing.T) {
ifa := &Tree{}
rfa := reflect.TypeOf(ifa)
fmt.Println("第一组输出:", rfa.Elem().Name(), rfa.Elem().Kind().String())
var ifb Forest = &Tree{}
ifc := new(Forest)
rfb := reflect.TypeOf(ifb)
rfc := reflect.TypeOf(ifc)
fmt.Println("第二组输出:", rfb.Elem().Name(), rfb.Elem().Kind().String())
fmt.Println("第二组输出:", rfc.Elem().Name(), rfc.Elem().Kind().String())
}
输出
第一组输出: Tree struct
第二组输出: Tree struct
第二组输出: Forest interface
- 第一组输出中
ifa
是具体的类型,所以返回本身的类型Tree
,对应的kind
为struct
。 - 第二组输出中
ifb
是interface
类型变量,绑定具体的类型对象实例,所以返回的是绑定的具体类型Forest
,对应的kind
为struct
。ifc
是interface
类型变量,且没有绑定任何具体的类型对象实例,所以返回的是本身的类型Forest
,对应的kind
为interface
。
2.2 ValueOf
再来看看 ValueOf
函数
// reflect/type.go
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i any) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
// Dummy annotation marking that the value x escapes,
// for use in cases where the reflect code is so clever that
// the compiler cannot follow.
func escapes(x any) {
if dummy.b {
dummy.x = x
}
}
var dummy struct {
b bool
x any
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
ValueOf
实现也很简单,若 i
为 nil
返回零值 Value{}
。否则就先调用 escapes(i)
确保值逃逸到堆上,然后调用 reflect.unpackEface
,先把 i interface{}
转换成 emptyInterface
,然后将具体类型和指针包装成 reflect.Value
结构体后返回。
这里对内存逃逸做个简单的说明。在 Golang
中,内存逃逸通常发生在以下情况:
1.变量的地址被返回给调用者。
2.变量的地址被赋值给全局变量或者其他包级变量。
3.变量的地址被传递给不透明的函数或方法(例如接口方法)。
上面的 dump
是个全局变量,当 x
赋值给全部变量的 x
属性时就会发生逃逸。其实,在变量 i
传给函数 escapes
时就已经发生了逃逸(见上述第三种情况),笔者在本机环境中(Golang 版本为 1.19.12
)测试也是如此:
package _0240619
import "testing"
func escapes(x any) {
if dummy.b {
dummy.x = x
}
}
var dummy struct {
b bool
x any
}
func TestEscapes(t *testing.T) {
i := 11
escapes(i)
}
执行 go tool compile -m escapes_test.go
,输出如下
escapes_test.go:5:6: can inline escapes
escapes_test.go:16:6: can inline TestEscapes
escapes_test.go:19:9: inlining call to escapes
escapes_test.go:5:14: leaking param: x
escapes_test.go:16:18: t does not escape
escapes_test.go:19:9: i escapes to heap
这里的 19
行就是 escapes(i)
,也就是传参的时候由于此函数入参是个 interface{}
因此发生了内存逃逸。
那什么时候仍然需要 escapes
函数?如果在某些特定情况下,编译器无法自动判定变量需要逃逸到堆上,而又需要强制变量逃逸,那么 escapes
函数仍然是有用的。它可以明确告诉编译器这个变量需要在堆上分配。
那为什么要在 ValueOf
中把变量的内存逃逸到堆上呢?源码上写的原因是 这样标记是为了防止反射代码写的过于高级,以至于编译器跟不上了。
这里我理解为 ValueOf
通常用于反射操作,在反射中我们经常需要确保被反射的对象在堆上,以便反射包能够安全地访问和修改这些对象。例如:
- 反射可能需要长时间持有对象的引用。
- 反射可能会传递对象到其他地方(例如,作为返回值)。
- 反射可能会在不同的
Goroutine
之间传递对象。
2.3 反射三定律
Golang
开派祖师之一 Rob Pike
于 2011
年写的 The Laws of Reflection,提出了反射三大定律
- 反射可以从接口值到反射对象
- 反射可以从反射对象中获得接口值
- 要修改反射对象,其值必须可设置
2.3.1 反射可以从接口值得到反射对象
反射第一定律即通过一个接口值,我们可以获取其对应的反射对象。可以通过 reflect.TypeOf
和 reflect.ValueOf
函数来实现。因为二者的入参都是 interface{}
,传参的时候会发生类型转换。
func TestReflectFirstLaw(t *testing.T) {
var x float64 = 3.4
tp := reflect.TypeOf(x) // 得到类型信息 reflect.Type
v := reflect.ValueOf(x) // 得到值信息 reflect.Value
fmt.Println("type:", tp) // 输出 float64
fmt.Println("value:", v) // 3.4
}
2.3.2 反射可以从反射对象中获得接口值
即通过一个反射对象,我们可以还原出其对应的接口值。可以通过 reflect.Value
的 Interface
方法来实现。
不过调用 reflect.Value.Interface
方法只能获得 interface{}
类型的变量,如果想要将其还原成最原始的状态还需要经过如下所示的显式类型转换:
func TestReflectSecondLaw(t *testing.T) {
var x float64 = 3.4
v := reflect.ValueOf(x)
y := v.Interface().(float64) // 将反射值转回为接口值
fmt.Println(y) // 3.4
}
仔细看,发现第一定律和第二定律是互为逆向的过程:
- 从接口值得到反射对象:
- 从基本类型到接口类型的类型转换(这里是
隐式
转换); - 从接口类型到反射对象的转换;
- 从基本类型到接口类型的类型转换(这里是
- 从反射对象的到接口值:
- 反射对象转换成接口类型;
- 通过
显式
类型转换变成原始类型;
2.3.3 要修改反射对象,其值必须可设置
如果我们想要更新一个 reflect.Value
,那么它持有的值一定是可以被更新的。
func TestReflectThirdLaw(t *testing.T) {
f := 3.14
v := reflect.ValueOf(f)
v.SetFloat(6.52)
}
运行后报如下错误
panic: reflect: reflect.Value.SetFloat using unaddressable value [recovered]
panic: reflect: reflect.Value.SetFloat using unaddressable value
这里给的提示信息是使用了不可寻址(unaddressable
)的 Value。
我们知道 Golang
的函数的参数都是值传递
的,这里 reflect.ValueOf(f)
传值后,会拷贝一个 f
的副本,与原来的 f
就没关系了,此时再改 f
的值就会报不可寻址。
修复也简单,传指针即可,虽然是值拷贝,但是指向的都是同一块内存地址。
func TestReflectThirdLaw(t *testing.T) {
f := 3.14
v := reflect.ValueOf(f)
if !v.CanSet() {
v = reflect.ValueOf(&f)
v = v.Elem()
}
v.SetFloat(6)
fmt.Println(v)}
2.4 实例
首先通过一个综合的示例,来把上面列举的 TypeOf
和 ValueOf
给串起来。
type Monster struct {
Name string `json:"name"`
Age int `json:"monster_age"`
Score float32
Sex string
}
func (m *Monster) Print() {
fmt.Println("---start---")
fmt.Println(m)
fmt.Println("---end---")
}
func (m *Monster) GetSum(n1, n2 int) int {
return n1 + n2
}
func (m *Monster) Set(name string, age int, score float32, sex string) {
m.Name = name
m.Age = age
m.Score = score
m.Sex = sex
}
func ProcStruct(a interface{}) {
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
valMethod := val
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
fmt.Println("expect struct")
return
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
num := val.NumField()
fmt.Printf("struct has %d field\n", num)
for i := 0; i < num; i++ {
fmt.Printf("Field %d: 属性为 = %v\n", i, typ.Field(i).Name)
fmt.Printf("Field %d: 值为 = %v\n", i, val.Field(i))
tagVal := typ.Field(i).Tag.Get("json")
if tagVal != "" {
fmt.Printf("Field %d: tag 为 %v\n", i, tagVal)
}
}
numOfMethod := valMethod.NumMethod()
fmt.Printf("struct has %d methods\n", numOfMethod)
valMethod.Method(1).Call(nil)
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(40))
res := valMethod.Method(0).Call(params)
fmt.Println("res = ", res[0].Int())
}
func TestReflectDemo(t *testing.T) {
monster := &Monster{
Name: "Cat",
Age: 400,
Score: 30.8,
Sex: "10",
}
ProcStruct(monster)
}
打印输出
struct has 4 field
Field 0: 属性为 = Name
Field 0: 值为 = Cat
Field 0: tag 为 name
Field 1: 属性为 = Age
Field 1: 值为 = 400
Field 1: tag 为 monster_age
Field 2: 属性为 = Score
Field 2: 值为 = 30.8
Field 3: 属性为 = Sex
Field 3: 值为 = 10
struct has 3 methods
---start---
&{Cat 400 30.8 10}
---end---
res = 50
这里需要关注下:
NumField()
,获取结构体字段的数量NumMethod
, 获取reflect.Value
可以访问的方法数量,这包括值接收者和指针接收者的方法- 值接收器:方法定义在值类型上。例如:
func (m Monster) Print()
- 指针接收器:方法定义在指针类型上。例如:
func (m *Monster) Print()
- 值接收器:方法定义在值类型上。例如:
来看看下面这段代码
type Foo struct {
Name string
}
func (f *Foo) Method1() {
fmt.Println("Method1 called")
}
func TestNum(t *testing.T) {
f := &Foo{}
v := reflect.ValueOf(f)
fmt.Println(v)
fmt.Println(v.Elem())
fmt.Println(v.Elem().NumField())
fmt.Println(v.NumMethod())
}
打印输出
&{}
{}
1
1
再结合上面列出的关注点,就很清楚二者要获取数量的注意点了。
NumField()
,针对的是结构体本身
的字段数量,若接收器(receiver
)是结构体指针,需要解引用指针,获取指针指向的reflect.Value
,也就是结构体NumMethod()
,用于获取某个reflect.Value
所代表的类型
的方法数量,即使类型为指针,也无需使用Elem()
进行解引用。
其次,再来个依据反射动态调用方法
type DynamicStruct struct {
Name string
Age int
Addr string
}
func (d *DynamicStruct) PrintName(ctx context.Context) error {
fmt.Println(d.Name)
return nil
}
func (d *DynamicStruct) PrintAge(ctx context.Context) error {
fmt.Println(d.Age)
return nil
}
func (d *DynamicStruct) PrintAddr(ctx context.Context) error {
fmt.Println(d.Addr)
return nil
}
func TestDynamic(t *testing.T) {
dynamic := &DynamicStruct{
Name: "molaifeng",
Age: 18,
Addr: "beijing",
}
ctx := context.Background()
{
method, exits := dynamic.GetMethod("Name")
if !exits {
return
}
_ = method(ctx)
}
{
method, exits := dynamic.GetMethod("hobby")
if !exits {
return
}
_ = method(ctx)
}
}
func (d *DynamicStruct) GetMethod(name string) (func(ctx context.Context) error, bool) {
methodName := "Print" + name
method := reflect.ValueOf(d).MethodByName(methodName)
if !method.IsValid() {
fmt.Println(fmt.Printf("%v not exist", methodName))
return nil, false
}
return method.Interface().(func(ctx context.Context) error), true
}
打印输出
molaifeng
Printhobby not exist20 <nil>
这里主要用到了 MethodByName
获取反射对象的方法,有了前面例子打底,这里就不做过多介绍。另外一个就是 Interface()
方法了,这个方法可以获取反射对象,但是呢,要还原成原本的类型,还需显示的转换,于是就有下面这个
method.Interface().(func(ctx context.Context) error), true
其实这个很好理解,看看具体 *DynamicStruct
具体绑定的方法,返回值是不是就是上面括号里的。
再来个简化版的
func TestInt(t *testing.T) {
num := 99
v := reflect.ValueOf(num)
i := v.Interface().(int)
fmt.Println(i)
}
最后,来个校验参数的例子
type Params struct {
Name *string
Age *int
Addr *string
}
func TestValidParam(t *testing.T) {
requiredParam := map[string]bool{
"Name": true,
"Age": true,
}
params := &Params{}
err := CheckParams(params, requiredParam)
if err != nil {
fmt.Println(err)
return
}
}
func CheckParams(params *Params, requiredParam map[string]bool) error {
val := reflect.ValueOf(params).Elem()
for fieldName := range requiredParam {
field := val.FieldByName(fieldName)
if isFieldNil(field) {
return fmt.Errorf("%v is required,actual nil", fieldName)
}
}
return nil
}
func isFieldNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Slice, reflect.Map, reflect.Ptr:
return v.IsNil()
default:
return false
}
}
打印如下
Name is required,actual nil
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 说说 golang 中的接口和反射
发表评论 取消回复