11.6 使用方法集与接口

第 10.6.3 节及例子 methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序:

示例 11.5 methodset2.go

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type List []int
  6. func (l List) Len() int {
  7. return len(l)
  8. }
  9. func (l *List) Append(val int) {
  10. *l = append(*l, val)
  11. }
  12. type Appender interface {
  13. Append(int)
  14. }
  15. func CountInto(a Appender, start, end int) {
  16. for i := start; i <= end; i++ {
  17. a.Append(i)
  18. }
  19. }
  20. type Lener interface {
  21. Len() int
  22. }
  23. func LongEnough(l Lener) bool {
  24. return l.Len()*10 > 42
  25. }
  26. func main() {
  27. // A bare value
  28. var lst List
  29. // compiler error:
  30. // cannot use lst (type List) as type Appender in argument to CountInto:
  31. // List does not implement Appender (Append method has pointer receiver)
  32. // CountInto(lst, 1, 10)
  33. if LongEnough(lst) { // VALID: Identical receiver type
  34. fmt.Printf("- lst is long enough\n")
  35. }
  36. // A pointer value
  37. plst := new(List)
  38. CountInto(plst, 1, 10) // VALID: Identical receiver type
  39. if LongEnough(plst) {
  40. // VALID: a *List can be dereferenced for the receiver
  41. fmt.Printf("- plst is long enough\n")
  42. }
  43. }

讨论

lst 上调用 CountInto 时会导致一个编译器错误,因为 CountInto 需要一个 Appender,而它的方法 Append 只定义在指针上。 在 lst 上调用 LongEnough 是可以的,因为 Len 定义在值上。

plst 上调用 CountInto 是可以的,因为 CountInto 需要一个 Appender,并且它的方法 Append 定义在指针上。 在 plst 上调用 LongEnough 也是可以的,因为指针会被自动解引用。

总结

在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以根据具体类型 P 直接辨识的:

  • 指针方法可以通过指针调用
  • 值方法可以通过值调用
  • 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
  • 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址

将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。

译注

Go 语言规范定义了接口方法集的调用规则:

  • 类型 *T 的可调用方法集包含接受者为 *TT 的所有方法集
  • 类型 T 的可调用方法集包含接受者为 T 的所有方法
  • 类型 T 的可调用方法集包含接受者为 *T 的方法

链接