Go 接口从入门到通透:一篇搞定你所有疑惑
刚学 Go 接口的时候你是不是也被这些问题搞懵过接口里的方法为什么没有接收者怎么知道谁实现了它为什么空接口什么类型都能装和普通接口有啥不一样给int包个自定义类型就能实现接口接口类型断言的时候panic了到底是怎么回事别慌这篇博客就把你之前所有的接口疑惑一次性讲透。一、接口到底是什么概念接口可以理解为一套能力规范 / 行为标准它只规定需要具备哪些能力、执行哪些行为但不定义具体的实现逻辑。举一个硬件领域的例子USB 接口标准USB 标准对应 Go 接口统一规定了数据传输规则、引脚定义、通信协议明确设备必须具备哪些交互能力U 盘、有线鼠标、机械键盘、移动硬盘对应 Go 中的各类自定义类型只要硬件设计完全遵循 USB 标准就可以接入电脑使用USB 标准不会约束设备内部电路、工作原理只校验是否符合既定规范。放到 Go 里接口就是一组方法签名的集合。比如下面的Person接口就定义了两种必须具备的行为// 定义一个“人”的接口契约能说话、能走路 type Person interface { Say(string) string // 说话接收一句话返回回应 Walk(int) // 走路接收步数无返回 }这就是接口的核心只定义 “要做什么”不管 “谁来做、怎么做”。二、基本接口声明、初始化、实现全流程1. 接口的声明声明接口的语法非常简单type 接口名 interface { 方法名(参数列表) 返回值列表 }这里刚好解答我们提出的前两个问题为什么接口里的方法没有接收者接收者是 “谁来实现这个方法” 的标识属于实现层面的细节而接口是抽象的契约不绑定任何具体类型所以不需要写接收者。接口里的参数名可以省略吗完全可以接口只看参数的类型和顺序不关心参数名。比如Say(string) string和Say(msg string) string是完全等价的写参数名只是为了可读性。2. 接口的实现Go 接口最灵魂的特性就是隐式实现你不用写implements不用声明 “我要实现这个接口”只要一个类型的方法集包含了接口里的所有方法它就自动实现了这个接口。就像下面写的例子// 自定义类型 type Number int // 实现Person接口的Say方法签名和接口完全一致 func (n Number) Say(s string) string { return bibibibibi } // 实现Person接口的Walk方法签名和接口完全一致 func (n Number) Walk(i int) { fmt.Println(can not walk) }Number只是个基于int的自定义类型但它实现了Person接口的两个方法就自动实现了Person接口没有任何额外声明。这里再补充两个关键的知识点必须是自定义类型吗对原生的int、string这些内置类型不能直接添加方法所以必须包一层自定义类型才能实现非空接口空接口除外后面会讲。什么是接口的超集如果一个接口 A 包含了接口 B 的所有方法还多了自己的方法那 A 就是 B 的超集实现了 A 的类型自动实现了 B。比如type Man interface { Exercise() // 额外的方法 Person // 嵌入Person接口继承它的所有方法 }实现了Man接口的类型自动实现了Person接口这也是 Go 里实现 “接口继承” 的方式。3. 接口的初始化接口变量本身是个 “容器”它的底层存着两个东西类型信息和值。初始化的时候只要把实现了接口的类型赋值给它就行var p Person // 声明一个Person接口变量初始值为nil p Number(10) // 赋值Number实现了Person完全合法这里要注意一个新手高频坑值接收者和指针接收者的区别如果方法是值接收者func (n Number) Say(...)那Number类型的值和指针都能赋值给接口变量如果方法是指针接收者func (n *Number) Say(...)那只有*Number类型的指针能赋值给接口变量。三、空接口空接口就是interface{}在 Go 1.18 里有个别名any它的定义就是type any interface{} // 一个方法都没有的接口1. 为什么空接口能存所有类型因为空接口没有任何方法要求所有类型的方法集都天然包含空接口的方法集空集是任何集合的子集所以所有类型都自动实现了空接口。所以你可以把任何类型赋值给空接口变量var a any a 100 // int a hello // string a []int{1,2,3} // 切片 a func(){} // 函数这也是为什么fmt.Println能打印任何东西 —— 它的参数就是...any类型。2. 空接口的坑类型断言与 panic空接口虽然万能但存进去之后你不知道里面存的是什么类型所以需要用类型断言把它转回来var a any 100 // 安全断言ok是bool值代表断言是否成功 num, ok : a.(int) if ok { fmt.Println(num) }如果不用安全断言直接写num : a.(string)而a里存的是int就会触发panic程序直接崩溃四、新手常踩的 5 个接口坑原生类型不能直接实现非空接口比如直接给int加方法会报错必须包成自定义类型类型断言不检查ok值直接断言失败会触发panic一定要用num, ok : a.(int)的形式指针接收者和值接收者搞混用指针接收者实现接口就只能用指针赋值给接口变量空接口不是 “无类型”空接口变量永远包含类型信息哪怕值是nil只要类型不为nil接口变量就不等于nil接口嵌入不是继承接口嵌入只是方法集的合并和类继承完全不同不会有父类的方法实现。五、最后总结Go 的接口本质上就是 “关注行为不关注类型”非空接口定义了明确的行为契约只有实现了所有方法的自定义类型才能赋值给它空接口没有任何行为要求是所有类型的超集能存任何值隐式实现没有implements关键字方法集匹配就是实现这也是 Go 多态的基础。