【问题标题】:How does Go bind methods to Objects?Go 如何将方法绑定到对象?
【发布时间】:2020-10-13 19:29:05
【问题描述】:

我几天前刚开始学习 Go。今天,我们在调试一段代码时发现了一些似乎与 Go 有悖常理的事情。

首先我们定义了一个接口和一个实现它的数据结构。

type Executer interface {
    Execute()
}

type whatever struct {
    name string
}

func (this *whatever) Execute() {
    log.Println(this.name)
}

现在考虑我有一个指向 whatever 的 nil 指针,我尝试调用方法 Execute。在我迄今为止使用的其他面向对象语言中,这将在调用方法时调用空指针错误(即w.Execute()),因为对象指针为空。有趣的是,在 Go 中,调用该方法时,当我尝试取消引用 this.name 时,Execute 方法出现空指针错误。为什么不在调用方法的时候?

func main() {
    var w *whatever
    w.Execute()
}

那么,我现在想要了解的是,这怎么可能?这是否意味着 Go 只在编译时进行早期方法绑定,而在运行时没有方法与特定对象的绑定?

【问题讨论】:

  • 接收者只是函数的一个“普通”参数。如果是指针,它可能是nil。在 Java 中,您也可以在 null 对象上调用静态方法。
  • 这是nil 接收者的用例:Test for nil values in nested stucts
  • @icza 有趣!我没有考虑过这个角度。虽然,当 Java 中的方法是静态的时,我可以通过执行 T.method() 调用它时遵循更好的模式,其中 T 是一个类型,而不是 obj.method() 其中 obj 是一个对象。看来我在 Go 中无法做到这一点,因为如果不将其绑定到特定的 struct,界面就会毫无生气。调用的风格让我感到困惑,让我相信 struct 的实例是目标,看来是 struct 类型绑定它。
  • 是的,因为 Go 更简单:没有 static、public、protected、private、package private 等修饰符。在 Go 中,只有导出和非导出。但是 Go 也有一些好处:方法表达式和值。见golang function alias on method receiver;和Pass method argument to function
  • @velkor 明白了。所有绑定都是在编译时完成的,对吧?现在已经澄清,方法的行为方式与其他语言(如 Java 或 C#)中的静态方法类似。

标签: go pointers methods binding


【解决方案1】:

接收者只是函数的一个“普通”参数。普通参数可以是指针类型。届时,您可以将nil 作为参数传递,这是完全有效的。您需要记住的只是不要取消引用nil 指针参数。这同样适用于特殊的接收器参数。如果是指针,可能是nil,只是不能取消引用。

Spec: Method declarations:

接收者是通过方法名称前面的额外参数部分指定的。

...该方法被称为绑定到它的接收器基类型,并且方法名称仅在selectors 中对于T*T 类型可见。

允许nil 接收器值不仅仅是不被禁止的事情,它还有实际用途。示例见Test for nil values in nested stucts

在 Java 中,您也可以在 null 对象上调用静态方法。确实你不能在 Go 中做同样的事情,因为 Go 没有 staticpublicprivate 等修饰符。在 Go 中只有导出和非导出方法(由第一个后者暗示他们的名字)。

但 Go 也提供了类似的功能:Method expressionsMethod values。如果你有一个方法mT 接收器类型,则表达式T.m 将是一个函数值,其签名包含m 的参数和结果类型“前缀”与接收器类型。

例如:

type Foo int

func (f Foo) Bar(s string) string { return fmt.Sprint(s, f) }

func main() {
    fooBar := Foo.Bar // A function of type: func(Foo, string) string
    res := fooBar(1, "Go")
    fmt.Println(res)
}

Foo.Bar 将是一个类型为func (Foo, string) string 的函数,您可以像调用任何其他普通函数一样调用它;而且您还必须将接收器作为第一个参数传递。上面的应用输出(在Go Playground上试试):

Go1

稍微“前进”,我们不需要将Foo.Bar存储在变量中,我们可以直接调用Foo.Bar

fmt.Println(Foo.Bar(1, "Go"))

哪个输出相同(在Go Playground 上试试)。现在这几乎看起来像 Java 中的 static 方法调用。

作为“最后”步骤,当我们对Foo 本身的值(而不是类型标识符)使用上面的表达式时,我们会得到方法值,这会保存接收者和a 的类型方法值不包括接收者,其签名将是方法的签名,我们可以调用它而无需传递接收者:

var f Foo = Foo(1)
bar := f.Bar
fmt.Println(bar("Go"))

这将再次输出相同的结果,请在Go Playground 上尝试。

查看相关问题:

Pass method argument to function

golang function alias on method receiver

【讨论】:

    猜你喜欢
    • 2014-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-20
    • 1970-01-01
    • 2019-11-15
    • 2013-10-21
    • 2019-08-04
    相关资源
    最近更新 更多