在第 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 的可调用方法集包含接受者为 _T 或 T 的所有方法集
    • 类型 T 的可调用方法集包含接受者为 T 的所有方法
    • 类型 T 的可调用方法集不包含接受者为 *T 的方法