问题引入
在Java中,我们可以通过重写Object#toString()方法,使得某个类的对象可以使用字符串的格式输出。在Golang中,我们也可以做到这点,就是通过给某个对象绑定String()方法。绑定了这个方法,那么这个对象就实现了fmt.Stringer接口。
但是有一个问题,绑定String()方法的时候,应该给指针还是值呢?对于一个struct来说,给值是正确答案。至少从结果来看,如果给值绑定了String()方法,那么使用fmt.Println这些方法输出的时候,无论传入的是结构体指针还是结构体值,String()方法都会被正确调用。
type Student struct {
Name string
Age int
}
func (student Student) String() string {
return fmt.Sprintf("Stu[Name=%s,Age=%d]", student.Name, student.Age)
}
func main() {
student := Student{
Name: "Jimmy",
Age: 7,
}
fmt.Println(student) // 输出:Stu[Name=Jimmy,Age=7]
studentPtr := &Student{
Name: "Tim",
Age: 4,
}
fmt.Println(studentPtr) // 输出:Stu[Name=Tim,Age=4]
}
如果把String()方法绑定在指针上(func (student *Student) String() string),那么输出就不一样了。
// 改成绑定给结构体指针
func (student *Student) String() string {
return fmt.Sprintf("Stu[Name=%s,Age=%d]", student.Name, student.Age)
}
func main() {
student := Student{
Name: "Jimmy",
Age: 7,
}
fmt.Println(student) // 输出:{Jimmy 7}
studentPtr := &Student{
Name: "Tim",
Age: 4,
}
fmt.Println(studentPtr) // 输出:Stu[Name=Tim,Age=4]
}
分析
如果查看Golang的源代码(fmt.print.go),我们对于println的调用会到达下面的代码
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
当继续追踪p.handleMethods(verb)的时候,关键的代码在这里
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
也就是说,print.go会判断传入的类型是否是一个fmt.Stringer,如果是,则会调用它的String()方法。
所以,关键点在于,传入的类型是否是一个fmt.Stringer。接下来,我们做一个测试
1)给指针绑定String()方法,然后判断其类型
func (student *Student) String() string {
return fmt.Sprintf("Stu[Name=%s,Age=%d]", student.Name, student.Age)
}
func main() {
student := Student{
Name: "Jimmy",
Age: 7,
}
var i1 interface{} = student
switch i1.(type) {
case fmt.Stringer:
fmt.Println("student Is a Stringer")
default:
fmt.Println("student Not a Stringer")
}
var i2 interface{} = &student
switch i2.(type) {
case fmt.Stringer:
fmt.Println("&student Is a Stringer")
default:
fmt.Println("&student Not a Stringer")
}
}
输出的结果是:
student Not a Stringer
&student Is a Stringer
2)给值绑定String()方法,然后判断其类型,输出则是
student Is a Stringer
&student Is a Stringer
结论
- 如果依赖fmt.Print方法族直接输出对象的字符串格式,要在对象的值上面绑定
String()方法 - 如果直接调用
Srtring()方法,则绑定在指针或值上都可以