[Go] 通过反射判断类型实现的接口方法是值接收器还是指针接收器

陪她去流浪 桃子 2019年09月21日 编辑 阅读次数:3472

最近有一个需求需要检验 Go 语言的接口方式是值接收器(Value Receiver)方法还是指针接收器(Pointer Receiver)方法。貌似 Go 没有直接提供的方式,所以需要手动实现。 Go 语言里面,值类型T和对应的指针类型*T有着不同的方法集。T是所有值接收器方法的集合,而*T是所有值接收器方法和所有指针接收器方法的集合。

获取类型的两种方法集

如果有以下的类型:

1
2
3
4
type S struct{}

func (s S) A()  {}
func (s *S) B() {}

则可以通过如下的反射代码来分别获取T*T类型的方法集:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	t := reflect.TypeOf(S{})
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Println("Value method:", m.Name)
	}

	fmt.Println()

	t = reflect.PtrTo(t)
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Println("Pointer method:", m.Name)
	}
}

以上代码会输出:

1
2
3
4
5
$ go run s.go
Value method: A

Pointer method: A
Pointer method: B

可以看到,值类型T只有值接收器方法,而指针类型*T同时有值接收器方法和指针接收器方法

所以总结一下:

  • 如果一个方法仅出现在指针类型的方法集中,那么它是指针接收器
  • 如果一个方法同时出现在指针类型值类型的方法集中,那么它是值接收器

通过反射判断方法是值接收器还是指针接收器

首先定义两个接口:

1
2
3
4
5
6
7
type IA interface {
	A()
}

type IB interface {
	B()
}

结构体还是上面代码的S结构体。

可以通过编写如下代码在运行时判断接收器的类型:

 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
func main() {
	// 拿到两种接口的反射类型信息
	tia := reflect.TypeOf((*IA)(nil)).Elem()
	tib := reflect.TypeOf((*IB)(nil)).Elem()

	t := reflect.TypeOf(S{})

	// 如果指针类型实现了...
	if reflect.PtrTo(t).Implements(tia) {
		// 并且值类型也实现了 ...
		if t.Implements(tia) {
			fmt.Println("A is value receiver")
		} else {
			fmt.Println("A is pointer receiver")
		}

	}

	// 如果指针类型实现了...
	if reflect.PtrTo(t).Implements(tib) {
		// 并且值类型也实现了 ...
		if t.Implements(tib) {
			fmt.Println("B is value receiver")
		} else {
			fmt.Println("B is pointer receiver")
		}
	}
}

上面的代码将输出:

1
2
3
$ go run s.go
A is value receiver
B is pointer receiver

参考

标签:Go · 反射