golang中的接口

x33g5p2x  于2021-11-12 转载在 Go  
字(2.3k)|赞(0)|评价(0)|浏览(633)

接口类型的定义与实现

接口是一种抽象类型,它仅包含一些方法签名,没有给出具体实现。

接口意味着约定。你拿到一个接口以后,你可以通过查看它包含的方法来知道它能做什么。

  1. type Speaker interface {
  2. Speak() string
  3. }

上面的代码定义了一个名为Speaker的接口,它包含一个方法。我们能从中看出来,Speak方法不需要提供参数就可以获取一个字符串,好像是要说点什么。

  1. type Employee struct {
  2. ID int
  3. Name, Address string
  4. }
  5. type EmployeeManager struct {
  6. Employee // 匿名成员
  7. ManagerLevel int
  8. }
  9. func (e *Employee) Speak() string {
  10. return fmt.Sprintf("[%d, %s], from %s", e.ID, e.Name, e.Address)
  11. }
  12. func (e *EmployeeManager) Speak() string {
  13. return fmt.Sprintf("%s , level %d", e.Employee.Speak(), e.ManagerLevel)
  14. }

上面的代码包含两个类型,这两个类型都实现了接口。实现接口的方式并不像有些语言一样需要明确的申明出来它implements了哪些接口,在golang中,只需要将接口中的所有方法全部实现了,就算实现了这个接口。

接口同样可以组合其它接口,就像struct中的匿名成员一样。

  1. var employee = Employee{
  2. ID: 3,
  3. Name: "fooEmployee",
  4. Address: "beijing",
  5. }
  6. var manager = EmployeeManager{
  7. Employee: Employee{
  8. ID: 2,
  9. Name: "fooManager",
  10. Address: "beijing",
  11. },
  12. ManagerLevel: 4,
  13. }
  14. var speaker Speaker
  15. // 如果写成 speaker = employee,则编译器会报错,因为方法的接收者类型是 *Employee 。
  16. speaker = &employee
  17. PrintSpeak(speaker)
  18. PrintSpeak(&manager)

在使用接口时,可以显式地定义一个Speaker类型的变量,这个变量的可赋值性调用方法更加严格。在方法调用时,一个结构体的变量可以调用以*T为接收者的方法,但在接口类型的赋值上,编译器是比较严格的。

万能类型 interface{}

interface{}这个类型,里面没有定义任何方法,也就意味着它没有做出任何承诺。看上去没啥用,其实它跟编译器的关系最好,可以赋值给它任意类型的变量。

  1. var any interface{}
  2. any = 1
  3. any = "hello"
  4. any = true
  5. any = map[string]int{}

fmt.Println这个函数的参数就是可变长度的interface{},也就意味着它的参数相传什么传什么,想传几个传几个。

  1. func Println(a ...interface{}) (n int, err error)

接口值

可以把接口类型想象成一个结构体,它包含两部分,动态类型和动态值。所以,如果一个接口被绑定了一个类型的话,即便这个类型的变量值是nil,但这个接口就不是nil了。

  1. var any interface{}
  2. fmt.Println(any == nil) // true
  3. var nilMap map[string]int = nil
  4. any = nilMap
  5. fmt.Println(nilMap == nil) // true
  6. fmt.Println(any == nil) // false! 此时的any,已经具备了明确的动态类型,即便动态类型的变量是nil

接口断言

那么,对于上面的问题,如何判断接口里面的值到底是个什么呢?答案就是使用接口断言。简单来讲,接口断言就是通过一个断言类型来求接口中对应的值,看下面的代码

  1. if realMap, ok := any.(map[string]int); ok {
  2. fmt.Println(realMap == nil)
  3. }

接口断言的语法是var.(T),如果var中包含T,则将T返回,ok为true。如果T是固定类型,那么第一个返回值就是它的值,而如果T还是一个接口类型,那么返回的仍然是一个接口值。

到目前为止,我们可以将一个较大的类型赋值给一个较小的接口类型,还可以通过接口断言来从较小的接口类型中尝试地拿到较大的类型,这个闭环就产生了。从结果上看,接口断言就类似类型检测和类型转换。

类型分支

既然存在interface{}这样的万能类型,可以将任何东西都赋值给它,也可以通过接口断言取出到底赋值了个什么,但如果可能的类型很多的话,代码会比较冗余,golang对此专门提供了语法支持,这种语法就叫类型分支。

  1. switch value := any.(type) {
  2. case nil:
  3. fmt.Println("value is nil")
  4. case string:
  5. fmt.Printf("%/s", value)
  6. case map[string]int:
  7. fmt.Println(value)
  8. }

需要注意的是,case nil是当any这个变量为nil,另外,这个神奇的value在不同的case语句块中的类型也是动态的,比如在case string中,value就是字符串类型,在case map[string]int中,value就是一个字典类型。

相关文章

最新文章

更多