Go :接口、泛型、错误处理
- 一、接口(Interface)
-
- [1.1 核心特性](#1.1 核心特性)
- [1.2 基础语法与使用](#1.2 基础语法与使用)
-
- [1.2.1 接口定义](#1.2.1 接口定义)
- [1.2.2 接口实现](#1.2.2 接口实现)
- [1.2.3 多态演示](#1.2.3 多态演示)
- [1.3 空接口 interface{}](#1.3 空接口 interface{})
- [1.4 接口组合](#1.4 接口组合)
- [1.5 类型断言 & 类型选择](#1.5 类型断言 & 类型选择)
-
- [1.5.1 基础类型断言(两种写法)](#1.5.1 基础类型断言(两种写法))
- [1.5.2 类型选择(type switch)](#1.5.2 类型选择(type switch))
- [1.6 接口高频踩坑](#1.6 接口高频踩坑)
- 二、泛型(Generics)
-
- [2.1 核心概念](#2.1 核心概念)
- [2.2 基础语法与内置约束](#2.2 基础语法与内置约束)
- [2.3 常用泛型实战案例](#2.3 常用泛型实战案例)
- [2.4 泛型高频踩坑](#2.4 泛型高频踩坑)
- 三、错误处理
-
- [3.1 基础:error 接口](#3.1 基础:error 接口)
-
- [3.1.1 创建普通错误](#3.1.1 创建普通错误)
- [3.1.2 函数返回错误(标准写法)](#3.1.2 函数返回错误(标准写法))
- [3.2 自定义错误类型](#3.2 自定义错误类型)
- [3.3 错误包装与解析(Go 1.13+)](#3.3 错误包装与解析(Go 1.13+))
-
- [3.3.1 错误包装](#3.3.1 错误包装)
- [3.3.2 错误解析](#3.3.2 错误解析)
- [3.4 panic & recover(处理致命错误)](#3.4 panic & recover(处理致命错误))
- [3.5 错误处理高频踩坑](#3.5 错误处理高频踩坑)
- 四、知识点速记表
目录
- 接口(Interface)
- 泛型(Generics)
- 错误处理(Error、panic、recover)
- 知识点速记
一、接口(Interface)
接口是 Go 实现行为契约、多态、解耦 的核心特性,它只定义一组方法签名,不包含字段与方法实现。Go 采用隐式实现,无需关键字声明实现关系,只要类型实现接口全部方法,就默认适配该接口。
1.1 核心特性
- 隐式实现 :无
implements关键字,实现接口所有方法即代表实现接口; - 接口变量 :存储动态类型 (实际类型)+动态值 ,零值为
nil; - 空接口
interface{}:可接收任意类型,是所有类型的父集; - 接口组合:支持嵌套多个接口,实现接口继承效果;
- 多态:同一接口变量可绑定不同实现类型,调用同名方法执行不同逻辑。
1.2 基础语法与使用
1.2.1 接口定义
语法格式:
go
type 接口名 interface {
方法名1(参数列表) 返回值列表
方法名2(参数列表) 返回值列表
// ... 多个方法
}
示例(图形面积、周长接口):
go
package main
import "math"
import "fmt"
// 定义 Shape 接口,约定两个行为:求面积、周长
type Shape interface {
Area() float64
Perimeter() float64
}
1.2.2 接口实现
任意自定义类型(常用结构体)实现接口全部方法,即自动实现接口。
go
// 圆形结构体
type Circle struct {
Radius float64
}
// 实现 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// 实现 Shape 接口的 Perimeter 方法
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func main() {
var s Shape // 定义接口类型变量
c := Circle{Radius: 5}
s = c // 结构体实例赋值给接口变量(多态)
fmt.Println("面积:", s.Area())
fmt.Println("周长:", s.Perimeter())
}
1.2.3 多态演示
不同结构体实现同一接口,接口变量切换实例,执行不同逻辑:
go
package main
import "fmt"
// 手机接口
type Phone interface {
call()
}
// 诺基亚结构体
type NokiaPhone struct{}
func (n NokiaPhone) call() {
fmt.Println("诺基亚:拨打电话")
}
// 苹果手机结构体
type IPhone struct{}
func (i IPhone) call() {
fmt.Println("iPhone:拨打电话")
}
func main() {
var p Phone
p = NokiaPhone{}
p.call()
p = IPhone{}
p.call()
}
1.3 空接口 interface{}
空接口没有定义任何方法 ,因此 Go 中所有类型都默认实现空接口,常用于通用参数、通用容器。
示例:通用打印函数
go
package main
import "fmt"
// 接收任意类型参数
func printValue(val interface{}) {
fmt.Printf("值:%v,类型:%T\n", val, val)
}
func main() {
printValue(100)
printValue("Go语言")
printValue(3.14)
printValue([]int{1,2,3})
}
1.4 接口组合
将多个接口嵌套,组合成新接口,实现接口复用:
go
package main
import "fmt"
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// 组合接口:同时拥有读、写能力
type ReadWriter interface {
Reader
Writer
}
// 文件结构体,同时实现两个子接口
type File struct{}
func (f File) Read() string {
return "读取文件内容"
}
func (f File) Write(data string) {
fmt.Println("写入内容:", data)
}
func main() {
var rw ReadWriter = File{}
fmt.Println(rw.Read())
rw.Write("测试数据")
}
1.5 类型断言 & 类型选择
接口变量存储的是抽象类型,如需还原底层具体类型,使用类型断言 ;批量判断类型使用 type switch。
1.5.1 基础类型断言(两种写法)
-
直接断言(失败触发
panic,不推荐)govar i interface{} = "测试字符串" str := i.(string) -
安全断言(搭配
ok判断,工程首选)gopackage main import "fmt" func main() { var i interface{} = 123 str, ok := i.(string) if ok { fmt.Println("断言成功:", str) } else { fmt.Println("类型不匹配") } }
1.5.2 类型选择(type switch)
批量匹配接口底层类型,适合多类型分支判断:
go
package main
import "fmt"
func checkType(val interface{}) {
switch v := val.(type) {
case int:
fmt.Println("整型:", v)
case string:
fmt.Println("字符串:", v)
case float64:
fmt.Println("浮点型:", v)
default:
fmt.Println("未知类型")
}
}
func main() {
checkType(666)
checkType("接口测试")
}
1.6 接口高频踩坑
- 方法未全部实现:结构体只实现接口部分方法,编译报错;
- 值接收 vs 指针接收 :接口方法如果是指针接收者,只能赋值结构体指针,不能赋值结构体实例;
- 空接口误用 :滥用
interface{}会丢失类型安全,尽量优先具体类型; - 断言不做判断 :直接断言不使用
ok,类型不匹配直接程序崩溃; - nil 接口判断 :接口变量 =
nil的条件是动态类型、动态值全部为 nil,仅值为 nil 时判断结果为 false。
二、泛型(Generics)
泛型是 Go 1.18 正式引入的特性,用于编写类型无关、可复用 的通用代码,避免为不同类型重复编写相同逻辑。核心组成:类型参数 + 类型约束。
2.1 核心概念
- 类型参数 :函数/结构体后用
[]声明占位类型,如[T]; - 类型约束:限制类型参数的可用类型(如任意类型、可比较类型、自定义类型集合);
- 类型推断:多数场景编译器可自动推导类型,无需显式声明。
2.2 基础语法与内置约束
2.2.1 通用语法
泛型函数
go
func 函数名[T 约束](参数列表) 返回值 { 函数体 }
泛型结构体
go
type 结构体名[T 约束] struct { 字段 }
2.2.2 三大内置约束
| 约束 | 含义 | 适用场景 |
|---|---|---|
any |
等价 interface{},允许任意类型 |
不限制类型的通用工具 |
comparable |
允许使用 ==/!= 比较的类型 |
Map 键、元素查找、去重 |
| 自定义联合约束 | 用 ` | ` 组合多种具体类型 |
2.2.3 示例演示
示例1:any 约束(任意类型通用函数)
go
package main
import "fmt"
// T 为任意类型
func PrintAny[T any](val T) {
fmt.Printf("值:%v,类型:%T\n", val)
}
func main() {
PrintAny(10)
PrintAny("泛型测试")
PrintAny(3.14)
}
示例2:comparable 约束(可比较类型)
实现通用切片元素查找:
go
package main
import "fmt"
// 仅支持可比较类型
func FindIndex[T comparable](slice []T, target T) int {
for idx, v := range slice {
if v == target {
return idx
}
}
return -1
}
func main() {
nums := []int{1,2,3}
fmt.Println(FindIndex(nums,2)) // 输出 1
}
示例3 自定义联合约束(限定数字类型)
go
package main
import "fmt"
// 自定义数字约束
type Number interface {
int | int8 | int16 | int32 | int64 |
uint | float32 | float64
}
// 通用加法函数
func Add[T Number](a, b T) T {
return a + b
}
func main() {
fmt.Println(Add(10,20))
fmt.Println(Add(1.5,2.5))
}
2.3 常用泛型实战案例
案例1:通用工具函数(交换、去重、判断包含)
go
package main
import "fmt"
// 交换两个任意类型变量
func Swap[T any](a, b T) (T, T) {
return b, a
}
// 切片去重
func Unique[T comparable](slice []T) []T {
m := make(map[T]bool)
var res []T
for _, v := range slice {
if !m[v] {
m[v] = true
res = append(res, v)
}
}
return res
}
func main() {
a, b := 1, 2
a, b = Swap(a, b)
fmt.Println(a, b)
old := []int{1,1,2,2,3}
fmt.Println(Unique(old))
}
案例2:泛型结构体(通用栈)
go
package main
import "fmt"
// 泛型栈
type Stack[T any] struct {
elements []T
}
// 入栈
func (s *Stack[T]) Push(val T) {
s.elements = append(s.elements, val)
}
// 出栈
func (s *Stack[T]) Pop() (T, bool) {
if len(s.elements) == 0 {
var zero T
return zero, false
}
idx := len(s)-1
val := s.elements[idx]
s.elements = s.elements[:idx]
return val, true
}
func main() {
// 整型栈
intStack := Stack[int]{}
intStack.Push(10)
fmt.Println(intStack.Pop())
}
2.4 泛型高频踩坑
- 版本限制:泛型仅 Go 1.18 及以上版本支持,低版本直接编译报错;
- 约束误用 :对非可比较类型(切片、Map)使用
comparable约束,编译失败; - 类型推断失效 :部分复杂场景需手动指定泛型类型
函数名[类型](参数); - 过度使用泛型:简单逻辑无需泛型,增加代码可读性负担;
- 方法约束:泛型类型的方法不能再额外添加类型参数。
三、错误处理
Go 不使用传统 try-catch 异常机制,采用显式错误返回 为核心,搭配 panic(致命恐慌)和 recover(恢复恐慌)处理严重异常,分为三类场景:普通业务错误、运行恐慌、自定义错误。
3.1 基础:error 接口
error 是 Go 内置接口,所有错误类型都必须实现该接口:
go
type error interface {
Error() string // 返回错误描述文本
}
3.1.1 创建普通错误
使用标准库 errors.New 创建基础错误:
go
package main
import "errors"
import "fmt"
func main() {
err := errors.New("参数非法")
fmt.Println(err)
}
3.1.2 函数返回错误(标准写法)
业务函数通常将 error 作为最后一个返回值 ,nil 代表无错误:
go
package main
import "errors"
import "fmt"
// 除法函数,除数为0返回错误
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
res, err := divide(10, 0)
if err != nil { // 优先判断错误
fmt.Println("执行失败:", err)
return
}
fmt.Println("结果:", res)
}
3.2 自定义错误类型
通过结构体实现 error 接口,扩展错误信息(错误码、详情等):
go
package main
import "fmt"
// 自定义除法错误
type DivideErr struct {
Dividend int // 被除数
Divisor int // 除数
}
// 实现 error 接口
func (e *DivideErr) Error() string {
return fmt.Sprintf("错误:%d 不能除以 %d", e.Dividend, e.Divisor)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideErr{Dividend: a, Divisor: b}
}
return a / b, nil
}
func main() {
_, err := divide(20, 0)
fmt.Println(err)
}
3.3 错误包装与解析(Go 1.13+)
3.3.1 错误包装
使用 fmt.Errorf("%w", 原始错误) 包装错误,保留原始错误链:
go
package main
import "errors"
import "fmt"
var ErrNotFound = errors.New("数据不存在")
func queryData(id int) error {
// 包装原始错误,追加上下文
return fmt.Errorf("查询id=%d 失败:%w", id, ErrNotFound)
}
3.3.2 错误解析
go
package main
import "errors"
import "fmt"
var ErrNotFound = errors.New("数据不存在")
func queryData(id int) error {
return fmt.Errorf("查询id=%d 失败:%w", id, ErrNotFound)
}
func main() {
err := queryData(1001)
// 判断是否是指定错误
if errors.Is(err, ErrNotFound) {
fmt.Println("根错误:数据不存在")
}
}
3.4 panic & recover(处理致命错误)
- panic :主动抛出运行恐慌,程序停止正常执行,逐层执行
defer;用于不可恢复的严重错误(如配置加载失败、核心依赖缺失); - recover :搭配
defer使用,捕获panic,恢复程序运行。
标准使用模板
go
package main
import "fmt"
func safeFunc() {
// 必须在 defer 匿名函数内调用 recover
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获恐慌:", r)
}
}()
// 触发恐慌
panic("数组下标越界")
}
func main() {
fmt.Println("程序开始")
safeFunc()
fmt.Println("程序继续执行") // 未崩溃,正常运行
}
3.5 错误处理高频踩坑
- 忽略错误返回值 :函数返回
error时不判断,隐藏线上隐患; - 错误判断顺序错误:先使用结果、再判断错误(错误时结果为零值);
- recover 使用错误 :
recover必须放在defer匿名函数内,嵌套/后置均无法捕获; - 滥用 panic :普通业务错误使用
error,panic仅用于致命场景; - 错误包装丢失根错误 :包装错误必须使用
%w,否则无法用errors.Is/As解析。
四、知识点速记表
| 模块 | 核心要点 | 高频踩坑 |
|---|---|---|
| 接口 | 隐式实现、空接口接收任意类型、类型断言/type switch、接口组合 | 方法实现不全、指针/值接收混淆、直接断言不判空 |
| 泛型 | 类型参数+约束、any/comparable/自定义约束、泛型函数/结构体 | Go1.18以下不支持、约束类型不匹配、过度泛型 |
| 错误处理 | error 接口、显式返回错误、%w包装错误、panic+recover | 忽略error、refer使用位置错误、普通场景滥用panic |