写在前面
本文是 Go 学习笔记系列的第四篇,介绍 Go 的接口和错误处理机制。接口是 Go 实现多态的核心,错误处理是 Go 的设计哲学(没有 try-catch)。前置知识:结构体和方法(第三篇)。
一、接口基础
1.1 定义接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 定义接口
type Speaker interface {
Speak() string
}
// 实现接口(隐式实现,不需要 implements 关键字)
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return d.Name + ":汪汪汪"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return c.Name + ":喵喵喵"
}
|
Go 的接口是隐式实现:只要一个类型实现了接口的所有方法,它就自动实现了该接口。不需要声明 implements。
1.2 使用接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 接口作为参数(多态)
func makeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
d := Dog{Name: "旺财"}
c := Cat{Name: "小橘"}
makeSound(d) // 旺财:汪汪汪
makeSound(c) // 小橘:喵喵喵
// 接口切片
animals := []Speaker{d, c}
for _, a := range animals {
fmt.Println(a.Speak())
}
}
|
1.3 接口变量
1
2
3
4
5
6
7
8
|
var s Speaker
s = Dog{Name: "旺财"}
fmt.Println(s.Speak())
// 接口变量包含两部分:
// 1. 类型信息(动态类型)
// 2. 值信息(动态值)
|
二、类型断言与类型选择
2.1 类型断言
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var s Speaker = Dog{Name: "旺财"}
// 断言(如果类型不对会 panic)
d := s.(Dog)
fmt.Println(d.Name)
// 安全断言(推荐)
d, ok := s.(Dog)
if ok {
fmt.Println("是 Dog:", d.Name)
}
// 错误的断言
c, ok := s.(Cat)
fmt.Println(ok) // false
|
2.2 类型选择(type switch)
1
2
3
4
5
6
7
8
9
10
|
func checkType(s Speaker) {
switch v := s.(type) {
case Dog:
fmt.Println("Dog:", v.Name)
case Cat:
fmt.Println("Cat:", v.Name)
default:
fmt.Println("未知类型")
}
}
|
三、常用接口
3.1 Stringer(类似 Java 的 toString)
1
2
3
4
5
6
7
8
9
10
11
12
|
type User struct {
Name string
Age int
}
// 实现 fmt.Stringer 接口
func (u User) String() string {
return fmt.Sprintf("%s (%d岁)", u.Name, u.Age)
}
user := User{Name: "张三", Age: 25}
fmt.Println(user) // 张三 (25岁)
|
3.2 error 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// error 是一个内置接口
type error interface {
Error() string
}
// 自定义错误(实现 Error() 方法即可)
type NotFoundError struct {
Resource string
ID int
}
func (e NotFoundError) Error() string {
return fmt.Sprintf("%s #%d 未找到", e.Resource, e.ID)
}
|
3.3 io.Reader 和 io.Writer
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// io.Reader 接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// io.Writer 接口
type Writer interface {
Write(p []byte) (n int, err error)
}
// 文件同时实现了 Reader 和 Writer
// net.Conn 也同时实现了两者
// 这两个接口是 Go I/O 的基础
|
3.4 空接口 interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 空接口没有任何方法,所有类型都实现了它
// 类似 Java 的 Object 或 Python 的 Any
func printAnything(v interface{}) {
fmt.Println(v)
}
printAnything(42)
printAnything("hello")
printAnything([]int{1, 2, 3})
// Go 1.18+ 可以用 any 代替 interface{}
func printAnything(v any) {
fmt.Println(v)
}
|
空接口要慎用,能确定类型就用具体类型或泛型。
四、错误处理
4.1 基本模式
Go 没有 try-catch,错误通过返回值传递:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 函数返回错误
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// 调用时检查错误
result, err := divide(10, 0)
if err != nil {
fmt.Println("出错:", err)
return
}
fmt.Println("结果:", result)
|
nil 表示没有错误。Go 的错误处理就是 if err != nil,写多了就习惯了。
4.2 创建错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import "errors"
import "fmt"
// 方式1:errors.New
err1 := errors.New("文件不存在")
// 方式2:fmt.Errorf(支持格式化,最常用)
err2 := fmt.Errorf("用户 %s 不存在", name)
// 方式3:自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("验证失败: %s — %s", e.Field, e.Message)
}
err3 := ValidationError{Field: "email", Message: "格式不正确"}
|
4.3 errors.Is 和 errors.As(Go 1.13+)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import "errors"
// 定义哨兵错误(Sentinel Error)
var (
ErrNotFound = errors.New("未找到")
ErrForbidden = errors.New("无权限")
)
// errors.Is:判断错误链中是否包含指定错误
err := someFunc()
if errors.Is(err, ErrNotFound) {
fmt.Println("资源不存在")
}
// errors.As:从错误链中提取指定类型的错误
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Println("字段:", valErr.Field)
}
|
4.4 错误包装(Wrapping)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 用 %w 包装错误(保留原始错误信息)
func readConfig(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("读取配置文件失败: %w", err)
}
// ...
return nil
}
// 调用方可以用 errors.Is / errors.As 追溯到原始错误
err := readConfig("config.yaml")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("配置文件不存在")
}
|
4.5 常见错误处理模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 模式1:逐层返回
func handler() error {
if err := validate(); err != nil {
return fmt.Errorf("参数校验失败: %w", err)
}
if err := process(); err != nil {
return fmt.Errorf("处理失败: %w", err)
}
return nil
}
// 模式2:错误后清理资源
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
// 模式3:记录日志后继续
if err := retryableOp(); err != nil {
log.Printf("操作失败,稍后重试: %v", err)
// 不返回,继续执行
}
|
五、panic 与 recover
5.1 panic
panic 用于不可恢复的严重错误,类似其他语言的异常抛出:
1
2
3
4
5
6
7
|
func mustParse(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(fmt.Sprintf("无法解析 %q 为整数", s))
}
return n
}
|
正常业务逻辑不要用 panic,应该返回 error。panic 适合程序初始化阶段的致命错误。
5.2 recover
recover 用于捕获 panic,只在 defer 中有效:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()
panic("出了大问题")
}
func main() {
safeCall()
fmt.Println("程序继续运行") // 会正常执行
}
|
5.3 panic vs error
1
2
3
4
5
6
|
error — 正常的错误处理方式,调用方决定怎么处理
panic — 不可恢复的错误,程序崩溃(除非 recover)
使用场景:
- 返回 error:文件不存在、网络超时、参数无效等可预期的错误
- panic:数组越界、空指针、初始化失败等程序 bug 或致命错误
|
六、接口组合
Go 通过接口组合实现类似继承的效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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
}
// 一个类型只需同时实现 Read 和 Write 就自动实现 ReadWriter
type File struct{}
func (f File) Read(p []byte) (int, error) { return 0, nil }
func (f File) Write(p []byte) (int, error) { return 0, nil }
var rw ReadWriter = File{} // 合法
|
七、小结
本文学习了 Go 的接口和错误处理:
- 接口的隐式实现和多态
- 类型断言和类型选择
- 常用接口(Stringer、error、Reader/Writer)
- 错误处理的惯用模式(if err != nil)
- errors.Is / errors.As / 错误包装
- panic 与 recover
- 接口组合
下一篇将学习 Go 的并发编程:goroutine、channel 和 context。