接口(interface)是Go语言中最重要的设计之一,它定义了一组方法签名,但不实现它们。Go中的接口实现是隐式的:只要一个类型实现了接口要求的所有方法,它自动就实现了该接口,无需显式声明。这种设计使得Go语言在保持静态类型安全的同时,又拥有了高度的灵活性和可扩展性。
使用type ... interface定义接口,指定方法集。任何类型只要拥有接口中声明的所有方法,就自动实现了该接口。
package main
import "fmt"
// 定义一个发声接口
type Speaker interface {
Speak() string
}
// 定义Dog结构体
type Dog struct {
Name string
}
// Dog实现Speak方法
func (d Dog) Speak() string {
return "汪汪!我是" + d.Name
}
// 定义Cat结构体
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "喵喵~我是" + c.Name
}
// 多态函数:接收任何实现了Speaker接口的类型
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "旺财"}
cat := Cat{Name: "咪咪"}
MakeSound(dog) // 汪汪!我是旺财
MakeSound(cat) // 喵喵~我是咪咪
}
无需像Java那样使用implements关键字,Go编译器会自动检查接口实现。
空接口没有定义任何方法,因此所有类型都实现了空接口。它可以表示任意类型的值,类似于其他语言中的any类型(Go 1.18+有any别名)。
func PrintAnything(v interface{}) {
fmt.Printf("值: %v, 类型: %T\n", v, v)
}
func main() {
PrintAnything(42)
PrintAnything("hello")
PrintAnything([]int{1,2,3})
PrintAnything(struct{ X, Y int }{10, 20})
}
fmt.Println、json.Unmarshal)。但使用空接口通常意味着需要类型断言。类型断言用于提取接口值的具体类型。语法:x.(T),其中x是接口类型的表达式。
var i interface{} = "Go语言"
// 安全断言:带检查,不会panic
s, ok := i.(string)
if ok {
fmt.Println("字符串:", s)
} else {
fmt.Println("不是字符串")
}
// 不安全断言(如果断言失败会panic)
// s2 := i.(int) // panic
value, ok := x.(T)形式是安全的;直接value := x.(T)失败时会触发panic,仅当确信类型匹配时使用。类型开关是用于判断接口动态类型的结构,语法类似于普通switch,但case后面跟类型。
func checkType(v interface{}) {
switch t := v.(type) {
case int:
fmt.Printf("整数: %d\n", t)
case string:
fmt.Printf("字符串: %s\n", t)
case bool:
fmt.Printf("布尔: %t\n", t)
case []int:
fmt.Printf("整数切片,长度: %d\n", len(t))
default:
fmt.Printf("未知类型: %T\n", t)
}
}
func main() {
checkType(100) // 整数: 100
checkType("hello") // 字符串: hello
checkType([]int{1,2}) // 整数切片,长度: 2
}
一个接口可以嵌入其他接口,最终接口包含所有嵌入接口的方法集。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
// 实现ReadWriter的类型必须同时实现Read和Write方法
接口变量存储两部分:动态类型和动态值。只有当两者都为nil时,接口才为nil。
var i interface{} // nil, nil -> i == nil
var p *int = nil
i = p // i 包含类型 *int, 值 nil -> i != nil
fmt.Println(i == nil) // false!接口不为nil,尽管内部指针为nil
// 常见陷阱:函数返回nil指针,但接口不为nil
func returnsError() error {
var p *MyError = nil
return p // 返回的error接口不为nil
}
if returnsError() != nil {
fmt.Println("这不是nil错误!") // 会执行
}
nil,而不是返回一个值为nil的具体类型指针。Go 1.18 引入了泛型,同时预定义了any作为interface{}的别名,二者等价。
var x any = 42
fmt.Println(x)
可以使用空标识符_在编译时检查类型是否实现了接口。
type MyInt int
func (m MyInt) String() string {
return strconv.Itoa(int(m))
}
// 编译时检查 MyInt 是否实现了 fmt.Stringer
var _ fmt.Stringer = MyInt(0)
package main
import "fmt"
// 操作接口
type Operation interface {
Apply(a, b int) int
Name() string
}
// 加法
type Add struct{}
func (Add) Apply(a, b int) int { return a + b }
func (Add) Name() string { return "加法" }
// 乘法
type Multiply struct{}
func (Multiply) Apply(a, b int) int { return a * b }
func (Multiply) Name() string { return "乘法" }
// 计算器
type Calculator struct {
ops []Operation
}
func (c *Calculator) Register(op Operation) {
c.ops = append(c.ops, op)
}
func (c *Calculator) Run(a, b int) {
for _, op := range c.ops {
fmt.Printf("%s: %d %d = %d\n", op.Name(), a, b, op.Apply(a, b))
}
}
func main() {
calc := &Calculator{}
calc.Register(Add{})
calc.Register(Multiply{})
calc.Run(6, 3)
}