接口
作者: ryan 发布于: 8/28/2025 更新于: 9/5/2025 字数: 0 字 阅读: 0 分钟
什么是接口?
接口是一种行为规范,是一系列方法的集合组成的类型,通过定义方法的集合来描述对象的行为标准。 定义一组未实现的函数声明。谁使用接口就是参照接口的方法定义实现它们。
谁实现了接口,就要把当前这个接口中所有未实现方法,一个不能少全部实现,才称为实现了该接口。也就是说,你是该接口类型。
type 接口名 interface {
方法1 (参数列表1) 返回值列表1
方法2 (参数列表2) 返回值列表2
...
}
接口命名习惯在接口名后面加上er后缀
参数列表、返回值列表参数名可以不写
如果要在包外使用接口,接口名应该首字母大写,方法要在包外使用,方法名首字母也要大写
接口中的方法应该设计合理,不要太多
Go语言的接口设计是非侵入式的,接口编写者无需知道接口会被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无需指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。
接口是约束谁应该具有什么功能,实现某接口的方法,就具有该接口的功能。
什么叫实现了该接口?
该接口的所有方法一个不落全部要实现,就是实现了该接口,我受到该接口约束,我只能乖乖的实现其所有方法。
一个结构体实现了一个接口声明的所有方法,就说结构体实现了该接口 一个结构体可以实现多个不同接口
以下例子实现满足run()
方法 就实现了Sport接口
package main
import "fmt"
type Sport interface {
run()
}
type Person struct {
name string
age int
}
func (p Person) run() {
fmt.Println("我跑")
}
func main() {
tom := Person{"tom", 20}
tom.run()
}
由于Person 并没有实现 Sport 接口中的所有方法,所以 tom 只能是 Person 类型 不能是 Sport 接口类型
package main
import "fmt"
type Sport interface {
run()
jump()
}
type Person struct {
name string
age int
}
func (p Person) run() {
fmt.Println("我跑")
}
func main() {
tom := Person{"tom", 20}
tom.run()
}
Person 实现了 Sport 接口中的所有方法,所以说Person 实现了 Sport 接口 tom 是 Person 的实例,也是接口的实例。同属于两类
package main
import "fmt"
type Sport interface {
run()
jump()
}
type Person struct {
name string
age int
}
func (p *Person) run() {
fmt.Println("我跑")
}
func (p *Person) jump() {
fmt.Println("我跳了")
}
func main() {
tom := Person{"tom", 20} //tom既属于Person类型,又属于Sport接口类型
tom.run()
}
swim 是 Person 自己的方法,不是接口的约束(该接口只有 run 和 jump 没有 swim)。不影响 Person 实现了接口
package main
import "fmt"
type Sport interface {
run()
jump()
}
type Person struct {
name string
age int
}
func (p *Person) run() {
fmt.Println("我跑")
}
func (p *Person) jump() {
fmt.Println("我跳了")
}
func (p *Person) swim() {
fmt.Println("我能游泳")
}
func main() {
tom := Person{"tom", 20} //tom既属于Person类型,又属于Sport接口类型
tom.run()
}
tom 既属于Person类型,又属于Driver接口类型
package main
import "fmt"
type Sport interface {
run()
jump()
}
type Driver interface {
drive()
}
type Person struct {
name string
age int
}
func (p *Person) run() {
fmt.Println("我跑")
}
func (p *Person) jump() {
fmt.Println("我跳了")
}
func (p *Person) swim() {
fmt.Println("我能游泳")
}
func (p *Person) drive() {
fmt.Println("我能驾驶")
}
func main() {
tom := Person{"tom", 20} //tom既属于Person类型,又属于Sport接口类型
tom.run()
tom.swim()
fmt.Println("~~~~~~~~~~~~~~~~~~~~")
var s Sport = &tom
s.run()
s.jump()
//s.swim() 不可以,因为Sport类没有swim方法
var d Driver = &tom //tom 既属于Person类型,又属于Driver接口类型
d.drive()
}
实例到底是什么类型?
实例属于某类型,如果该类型正好实现了某接口,也说该实例是该接口类型的。
可以实现多少个接口?
不限
Receiver和接口
当接收者为值类型(T)时:
- 接口可以接受 T 或 *T 类型的值
- 编译器会自动生成对应的指针接收者方法
- 通过语法糖,可以:
• 使用值实例调用指针接收者方法
• 使用指针实例调用值接收者方法
当接收者为指针类型(*T)时:
- 接口只能接受 *T 类型的值
- 编译器不会自动生成值接收者方法
- 直接使用值类型实例无法满足接口要求
语法糖规则:
- 方法调用时Go会自动转换值/指针类型
- 但接口实现时需要严格匹配类型要求
package main
import (
"fmt"
"math")
// 定义接口
type Shape interface {
Area() float64
}
// 情况1:值类型的接收者(T)
type Rectangle struct {
Width, Height float64
}
// 值接收者方法:编译器会自动生成对应的指针接收者方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 情况2:指针类型的接收者(*T)
type Circle struct {
Radius float64
}
// 指针接收者方法:编译器不会自动生成值接收者方法
func (c *Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func main() {
// ========== Rectangle测试(值接收者) ========== var s1 Shape
// 使用值类型实例
rVal := Rectangle{3, 4}
s1 = rVal // 合法
fmt.Println("Rectangle值实例:", s1.Area()) // 输出 12
// 使用指针类型实例
rPtr := &Rectangle{5, 6}
s1 = rPtr // 合法:因为编译器自动为值接收者生成了指针接收者方法
fmt.Println("Rectangle指针实例:", s1.Area()) // 输出 30
// ========== Circle测试(指针接收者) ========== var s2 Shape
// 使用指针类型实例
cPtr := &Circle{7}
s2 = cPtr // 合法
fmt.Println("Circle指针实例:", s2.Area()) // 输出 ≈153.938
// 使用值类型实例
cVal := Circle{8}
// s2 = cVal // 非法!取消注释会报错:Circle does not implement Shape(Area方法需要指针接收者)
// ========== 语法糖测试 ========== // 值类型实例调用指针接收者方法(自动转换)
cVal.Area() // 合法:Go自动转换为(&cVal).Area()
// 指针类型实例调用值接收者方法(自动解引用)
rPtr.Area() // 合法:Go自动转换为(*rPtr).Area()
}
/* 关键点总结:
1. 当接收者为值类型(T)时:
- 接口可以接受 T 或 *T 类型的值
- 编译器会自动生成对应的指针接收者方法
- 通过语法糖,可以:
• 使用值实例调用指针接收者方法
• 使用指针实例调用值接收者方法
2. 当接收者为指针类型(*T)时:
- 接口只能接受 *T 类型的值
- 编译器不会自动生成值接收者方法
- 直接使用值类型实例无法满足接口要求
3. 语法糖规则:
- 方法调用时Go会自动转换值/指针类型
- 但接口实现时需要严格匹配类型要求
*/
接口嵌入
除了结构体可以嵌套,接口也可以。接口嵌套组合成了新接口。Go建议定义小接口,组合为大接口。
Sporter接口是Runner、Jumper接口组合而成,也就是说它拥有run、jump方法声明。
type runner interface {
run()
}
type jumper interface {
jump()
}
type Sporter interface {
runner
jumper}
空接口
空接口,实际上是空接口类型,写作 interface {}
。为了方便使用,Go语言为它定义一个别名any类型,即 type any = interface{}
。
空接口,没有任何方法声明,因此,任何类型都无需显式实现空接口的方法,任何类型都满足这个空接口的要求。那么,任何类型的值都可以看做是空接口类型。
任何实例都实现了该接口 int 类型实现了该接口, string 类型也实现了该接口 切片实现了该接口 等等...
a := 500 , a 既是 int 类型的实例,又是空接口类型的实例?
a := "abc" a 既是 string 类型的实例,又是空接口类型的实例
a := []int{}, a 既是 切片 类型的 int[] 实例,又是空接口类型的实例
type any = interface{ }
空接口变量可以接受所有类型实例
package main
import "fmt"
func main() {
type Runner interface {
run()
}
type A interface{} //任何实例都实现了该接口
var a = 500 //a 既是int类型的实例,又是空接口类型的实例
var b interface{} //b是空接口类型变量
b = a //var b interface{} = a
fmt.Printf("%T %[1]v\n", a)
fmt.Printf("%T %[1]v\n", b)
c := "abcd"
b = c
fmt.Printf("%T %[1]v\n", c)
fmt.Printf("%T %[1]v\n", b)
}
运行结果
int 500
int 500
string abcd
string abcd
创建一个元素类型不一样的切片
//s := make([]interface{},0)
s := []interface{}{"111"}
s = append(s, 1)
s = append(s, "abc")
s = append(s, []string{})
s = append(s, nil)
s = append(s, map[string]int{})
fmt.Println(s)
运行结果
[111 1 abc [] <nil> map[]]
类型断言
类型接口断言主要用于对接口类型变量的类型进行断言。
接口类型断言(Type Assertions)可以将接口转换成另外一种接口,也可以将接口转换成另外的类型。 接口类型断言格式 t := i.(T)
i代表接口变量
T表示转换目标类型
t代表转换后的变量
断言失败,也就是说 i 没有实现T接口的方法则panic
t, ok := i.(T) ,则断言失败不panic,通过ok是true或false判断i是否是T类型接口
t, ok := b.(string)
这种写法,如果失败,ok 为 false
,t
不值得使用,如果成功 ok 为 true
,t
的值,可以使用
var b interface{} = 500 //b 是空接口类型的变量
fmt.Printf("%T %[1]v\n", b)
// b.(类型) 类型断言
//fmt.println(b.(string)) //转换失败会提示Panic
if t, ok := b.(int); ok {
fmt.Println("断言转换成功", t, ok)
} else {
fmt.Println("断言转换失败", t, ok)
}
type-switch
可以使用特殊格式来对接口做多种类型的断言。
b.(type)
只能用在switch中。
var b interface{} = 500
fmt.Printf("%T %[1]v\n", b)
//type-switch
switch v := b.(type) {
case string:
fmt.Println("字符串")
case int:
fmt.Println("整形")
case float64:
fmt.Println("浮点型")
case bool:
fmt.Println("布尔型")
case nil:
fmt.Println("nil")
case []string:
fmt.Println("字符串切片",v)
case []int:
fmt.Println("整形切片")
default:
fmt.Println("其他类型")
}