Go语言嵌入结构体的String()方法异常调用行为疑问
核心原因涉及Go的两个基础规则:方法提升规则、fmt包的打印逻辑
先解释你观察到的三种输出表现
首先明确一个前提:如果一个结构体有多个匿名嵌入字段,且多个嵌入字段存在同名方法,这个方法不会被提升到外层结构体的方法集,也就意味着外层结构体不会自动实现对应接口。
- 第一种场景:
Person和TaxPayer都定义了String()方法
此时两个嵌入类型的String()方法存在冲突,不会提升到Engineer结构体,因此Engineer本身没有实现fmt.Stringer接口。fmt.Println打印未实现Stringer的结构体时,会按顺序遍历打印结构体的所有字段值:
- 第一个嵌入字段
Person自己实现了Stringer,打印时调用它的String()方法输出name: John Doe, age: 35 - 第二个嵌入字段
TaxPayer自己实现了Stringer,打印时调用它的String()方法输出3 - 第三个普通字段
Specialization直接输出值Construction
拼起来就是你看到的{name: John Doe, age: 35 3 Construction}。
第二种场景:移除
Person.String(),仅保留TaxPayer.String()
此时只有一个嵌入类型存在String()方法,没有冲突,TaxPayer的String()方法会被提升到Engineer的方法集,Engineer实现了fmt.Stringer接口。fmt.Println会直接调用Engineer的String()方法(也就是提升过来的TaxPayer.String()),所以输出3。第三种场景:两个
String()都移除
此时Engineer和它的所有嵌入字段都没有实现fmt.Stringer,fmt.Println走最基础的结构体打印逻辑,直接输出所有字段的原始值,就得到了{{John Doe 35} {3} Construction}。
为什么自定义Foo()方法会报歧义,String()看起来不会?
你产生这个疑问的核心原因是:你从来没有主动调用过engineer.String()。
当两个嵌入类型都有同名方法时,只要你主动调用外层结构体的这个方法,不管方法名是String还是Foo,都会报ambiguous selector的编译错误。你可以自己加一行fmt.Println(engineer.String())测试,和调用Foo()的报错完全一致。
你之前的测试里String()没有触发报错,是因为fmt包并没有直接调用engineer.String(),只是走默认逻辑逐个打印字段而已,根本没有触发选择器的歧义检查。
内容的提问来源于stack exchange,提问作者Sam Chats

