关于Golang中的String方法,值绑定?指针绑定?

问题引入

在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()方法,则绑定在指针或值上都可以
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容