Skip to content

接口

作者: ryan 发布于: 8/28/2025 更新于: 9/5/2025 字数: 0 字 阅读: 0 分钟

什么是接口?

接口是一种行为规范,是一系列方法的集合组成的类型,通过定义方法的集合来描述对象的行为标准。 定义一组未实现的函数声明。谁使用接口就是参照接口的方法定义实现它们。

谁实现了接口,就要把当前这个接口中所有未实现方法,一个不能少全部实现,才称为实现了该接口。也就是说,你是该接口类型。

go
type 接口名 interface {

方法1 (参数列表1) 返回值列表1

方法2 (参数列表2) 返回值列表2

...

}
  • 接口命名习惯在接口名后面加上er后缀

  • 参数列表、返回值列表参数名可以不写

  • 如果要在包外使用接口,接口名应该首字母大写,方法要在包外使用,方法名首字母也要大写

  • 接口中的方法应该设计合理,不要太多

Go语言的接口设计是非侵入式的,接口编写者无需知道接口会被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无需指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

接口是约束谁应该具有什么功能,实现某接口的方法,就具有该接口的功能。

什么叫实现了该接口?

该接口的所有方法一个不落全部要实现,就是实现了该接口,我受到该接口约束,我只能乖乖的实现其所有方法。

一个结构体实现了一个接口声明的所有方法,就说结构体实现了该接口 一个结构体可以实现多个不同接口

以下例子实现满足run() 方法 就实现了Sport接口

go
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 接口类型

go
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 的实例,也是接口的实例。同属于两类

go
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 实现了接口

go
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接口类型

go
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和接口

  1. 当接收者为值类型(T)时:

    • 接口可以接受 T 或 *T 类型的值
    • 编译器会自动生成对应的指针接收者方法
    • 通过语法糖,可以:
      • 使用值实例调用指针接收者方法
      • 使用指针实例调用值接收者方法
  2. 当接收者为指针类型(*T)时:

    • 接口只能接受 *T 类型的值
    • 编译器不会自动生成值接收者方法
    • 直接使用值类型实例无法满足接口要求
  3. 语法糖规则:

    • 方法调用时Go会自动转换值/指针类型
    • 但接口实现时需要严格匹配类型要求
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方法声明。

go
type runner interface {  
    run()  
}  
  
type jumper interface {  
    jump()  
}  
  
type Sporter interface {  
    runner  
    jumper}

空接口

空接口,实际上是空接口类型,写作 interface {} 。为了方便使用,Go语言为它定义一个别名any类型,即 type any = interface{}

空接口,没有任何方法声明,因此,任何类型都无需显式实现空接口的方法,任何类型都满足这个空接口的要求。那么,任何类型的值都可以看做是空接口类型。

任何实例都实现了该接口 int 类型实现了该接口, string 类型也实现了该接口 切片实现了该接口 等等...

go
a := 500 , a 既是 int 类型的实例,又是空接口类型的实例?

a := "abc" a 既是 string 类型的实例,又是空接口类型的实例

a := []int{}, a 既是 切片 类型的 int[] 实例,又是空接口类型的实例

type any = interface{ }

空接口变量可以接受所有类型实例

go
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)  
      
}

运行结果

go
int 500
int 500
string abcd
string abcd

创建一个元素类型不一样的切片

go
//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)

运行结果

go
[111 1 abc [] <nil> map[]]

类型断言

类型接口断言主要用于对接口类型变量的类型进行断言。

接口类型断言(Type Assertions)可以将接口转换成另外一种接口,也可以将接口转换成另外的类型。 接口类型断言格式 t := i.(T)

go
i代表接口变量 
T表示转换目标类型 
t代表转换后的变量 
断言失败,也就是说 i 没有实现T接口的方法则panic 
t, ok := i.(T) ,则断言失败不panic,通过ok是true或false判断i是否是T类型接口

t, ok := b.(string) 这种写法,如果失败,ok 为 falset 不值得使用,如果成功 ok 为 truet 的值,可以使用

go
    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中。

go
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("其他类型")  
}