做剧情网站侵权吗,网站后台免费模板,昭通昭阳区城乡建设管理局网站,哪里做网站一套一百1、接口
1.1 类型
Golang 中的接口是一组方法的签名#xff0c;是实现多态和反射的基础。
type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表
}不同于 Java 语言#xff0c;使用 implements 关键字显示的实现接口。Golang 接口的实现都是…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 eface struct {_type *_typedata 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 iotakindIntkindInt8kindInt16kindInt32kindInt64kindUintkindUint8kindUint16kindUint32kindUint64kindUintptrkindFloat32kindFloat64kindComplex64kindComplex128kindArraykindChankindFunckindInterfacekindMapkindPtrkindSlicekindStringkindStructkindUnsafePointerkindDirectIface 1 5kindGCProg 1 6kindMask (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 uint8type eface struct {_type *_typedata unsafe.Pointer
}type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32tflag tflagalign uint8fieldAlign uint8kind 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 *bytestr nameOffptrToThis typeOff
}func TestEFace(t *testing.T) {var i interface{} 3.14e : (*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 *itabdata 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]bytefun [1]uintptr // 是一个函数指针它指向的是具体类型的函数方法
}itab 结构体是非空接口的核心组成部分占 32 字节着重看下 inter 和 _type 属性。
type imethod struct {name nameOffityp typeOff
}type interfacetype struct {typ _type // 类型元信息pkgpath name // 包路径和描述信息等等mhdr []imethod // 方法
}inter 存储的是非空接口自己类型相关数据因为 Golang 中函数方法是以包为单位隔离的。所以 interfacetype 除了保存 _type 还需要保存包路径等描述信息。mhdr 存的是各个 interface 函数方法在段内的偏移值 offset知道偏移值以后才方便调用。
_type 在上一节空接口已详细介绍过存储的是接口类型的元信息这里就不展开。
这里还要说下 tab 中 fun 属性存储的是指向实现非空接口类型的方法数组。
type iface struct {tab *itabdata unsafe.Pointer
}type itab struct {inter *interfacetype_type *_typehash uint32 // copy of _type.hash. Used for type switches._ [4]bytefun [1]uintptr // variable sized. fun[0]0 means _type does not implement inter.
}type imethod struct {name nameOffityp typeOff
}type interfacetype struct {typ _typepkgpath namemhdr []imethod
}type name struct {bytes *byte
}type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32tflag tflagalign uint8fieldAlign uint8kind 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 *bytestr nameOffptrToThis 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 snoEmpty : (*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 *Studentreturn 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 *rtypeword 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 uintptrptrdata uintptr // number of bytes in the type that can contain pointershash uint32 // hash of type; avoids computation in hash tablestflag tflag // extra type information flagsalign uint8 // alignment of variable with this typefieldAlign uint8 // alignment of struct field with this typekind 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) boolgcdata *byte // garbage collection datastr nameOff // string formptrToThis 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 boolx any
}// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {e : (*emptyInterface)(unsafe.Pointer(i))// NOTE: dont read e.word until we know whether it is really a pointer or not.t : e.typif 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 _0240619import testingfunc escapes(x any) {if dummy.b {dummy.x x}
}var dummy struct {b boolx any
}func TestEscapes(t *testing.T) {i : 11escapes(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.4tp : reflect.TypeOf(x) // 得到类型信息 reflect.Typev : reflect.ValueOf(x) // 得到值信息 reflect.Valuefmt.Println(type:, tp) // 输出 float64fmt.Println(value:, v) // 3.4}2.3.2 反射可以从反射对象中获得接口值
即通过一个反射对象我们可以还原出其对应的接口值。可以通过 reflect.Value 的 Interface 方法来实现。
不过调用 reflect.Value.Interface 方法只能获得 interface{} 类型的变量如果想要将其还原成最原始的状态还需要经过如下所示的显式类型转换
func TestReflectSecondLaw(t *testing.T) {var x float64 3.4v : reflect.ValueOf(x)y : v.Interface().(float64) // 将反射值转回为接口值fmt.Println(y) // 3.4
}仔细看发现第一定律和第二定律是互为逆向的过程
从接口值得到反射对象 从基本类型到接口类型的类型转换(这里是隐式转换)从接口类型到反射对象的转换 从反射对象的到接口值 反射对象转换成接口类型通过显式类型转换变成原始类型
2.3.3 要修改反射对象其值必须可设置
如果我们想要更新一个 reflect.Value那么它持有的值一定是可以被更新的。
func TestReflectThirdLaw(t *testing.T) {f : 3.14v : 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.14v : 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:nameAge int json:monster_ageScore float32Sex 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 namem.Age agem.Score scorem.Sex sex
}func ProcStruct(a interface{}) {typ : reflect.TypeOf(a)val : reflect.ValueOf(a)valMethod : valif 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.Valueparams 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 stringAge intAddr 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 namemethod : 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 : 99v : reflect.ValueOf(num)i : v.Interface().(int)fmt.Println(i)}最后来个校验参数的例子
type Params struct {Name *stringAge *intAddr *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