文章

阶段3 泛型与接口 - 代码实例集

阶段 3 泛型与接口 · 代码实例集

覆盖:接口基础 / 接口设计哲学 / 反射 / 泛型 / 接口 vs 泛型抉择。 配合 02-语言特性/04-接口与反射.md05-泛型.md 食用。


1. 接口基础

1.1 隐式实现(无需 implements 关键字)

type Speaker interface {
	Speak() string
}

type Dog struct{}
func (d Dog) Speak() string { return "woof" }

type Cat struct{}
func (c Cat) Speak() string { return "meow" }

func announce(s Speaker) { fmt.Println(s.Speak()) }

func main() {
	announce(Dog{}) // woof
	announce(Cat{}) // meow
}

结构体只要实现了接口所有方法,就自动满足接口,无需声明。

1.2 空接口与 any

var x interface{} = 42
var y any = "hello" // any 是 interface{} 的别名,完全相同
fmt.Println(x, y)

空接口可装任意类型,但取出来要用类型断言。

1.3 类型断言 + 类型 switch

func describe(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Println("int:", v)
	case string:
		fmt.Println("string:", v)
	default:
		fmt.Printf("unknown: %T\n", v)
	}
}
// 安全断言
if s, ok := i.(string); ok {
	fmt.Println(s)
}

2. 接口设计哲学

2.1 小接口(Go 的精髓)

// 标准库 io.Reader / io.Writer 就是典范:单一方法,随意组合
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 } // 组合

原则:接口越小越易实现、越易复用。别一上来就定义大接口。

2.2 值接收者 vs 指针接收者

type Counter struct{ n int }
func (c Counter) Get() int   { return c.n }        // 值接收:副本
func (c *Counter) Inc()      { c.n++ }             // 指针接收:改原值

func main() {
	var c Counter
	c.Inc()                 // Go 自动取地址 (&c).Inc()
	fmt.Println(c.Get())    // 1
}
  • 值接收者:方法内改不动原值,适合只读/小结构体。
  • 指针接收者:改原值,或大结构体(避免拷贝)。
  • 一致性:一个类型的方法集尽量统一用值或统一用指针。

2.3 接口零值与 nil 陷阱

var s Speaker      // s 是 nil 接口
fmt.Println(s == nil) // true
// 但若底层类型指针为 nil,接口非 nil:
var d *Dog
var sp Speaker = d
fmt.Println(sp == nil) // false!接口含 (type=*Dog, value=nil)
// 调用 sp.Speak() 会因方法接收者为 nil 而 panic

3. 反射(reflect)

3.1 TypeOf / ValueOf

import "reflect"

func main() {
	x := 42
	t := reflect.TypeOf(x)   // int
	v := reflect.ValueOf(x)  // 42
	fmt.Println(t, v, v.Int())
}

3.2 修改值(必须传指针)

func main() {
	x := 10
	v := reflect.ValueOf(&x)      // 传指针
	v.Elem().SetInt(20)           // Elem() 取指向的值,再 Set
	fmt.Println(x)                // 20
}
// ❌ reflect.ValueOf(x).SetInt(20) 会 panic:x 是值拷贝,不可寻址

3.3 实战:解析结构体 tag(JSON/校验常用)

type User struct {
	Name string `json:"name" validate:"required"`
	Age  int    `json:"age"`
}

func printTags(u interface{}) {
	t := reflect.TypeOf(u)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Println(f.Name, "→", f.Tag.Get("json"), f.Tag.Get("validate"))
	}
}
// 输出:Name → name required  /  Age → age

反射代价高,仅在序列化、ORM、校验等通用框架层使用,业务代码优先泛型。


4. 泛型(Go 1.18+)

4.1 泛型函数

func Max[T int | float64 | string](a, b T) T {
	if a > b { return a }
	return b
}
fmt.Println(Max(3, 7), Max(2.1, 1.9), Max("a", "z"))

4.2 内置约束 comparable / any

// comparable:可用 == / != 的类型(map key、去重等)
func Index[T comparable](s []T, target T) int {
	for i, v := range s {
		if v == target { return i }
	}
	return -1
}

// any:任意类型
func PrintAll[T any](s []T) {
	for _, v := range s { fmt.Println(v) }
}

4.3 自定义约束(接口语法)

type Number interface {
	~int | ~int64 | ~float64 // ~ 表示底层类型匹配
}
func Sum[T Number](nums []T) T {
	var total T
	for _, n := range nums { total += n }
	return total
}

~int 让自定义类型 type MyInt int 也满足约束。

4.4 泛型切片工具(写一次,类型安全复用)

func Map[T, U any](s []T, f func(T) U) []U {
	r := make([]U, len(s))
	for i, v := range s { r[i] = f(v) }
	return r
}
func Filter[T any](s []T, pred func(T) bool) []T {
	var r []T
	for _, v := range s {
		if pred(v) { r = append(r, v) }
	}
	return r
}
nums := []int{1, 2, 3, 4}
doubled := Map(nums, func(n int) int { return n * 2 }) // [2 4 6 8]
evens := Filter(nums, func(n int) bool { return n%2 == 0 }) // [2 4]

4.5 泛型结构体

type Stack[T any] struct { items []T }
func (s *Stack[T]) Push(v T)  { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if len(s.items) == 0 { return zero, false }
	v := s.items[len(s.items)-1]
	s.items = s.items[:len(s.items)-1]
	return v, true
}

4.6 类型推导(大部分情况不用显式写类型参数)

Sum([]int{1, 2, 3})       // 推导出 T=int
Sum([]float64{1.1, 2.2})  // T=float64

5. 接口 vs 泛型:怎么选

场景选接口选泛型
方法集多态(不同实现同一行为)✅ 运行时多态
容器/算法(同逻辑多类型)✅ 编译期类型安全、零开销
需要 any 通用存储
需要数值运算(+, >)✅ 约束表达
鸭子类型、解耦调用方

经验:能泛型就泛型(类型安全 + 性能好),需要运行时多态才用接口。


自测题

  1. Go 的接口实现是隐式还是显式?有什么好处?
  2. 值接收者和指针接收者的区别?什么时候用指针?
  3. 反射修改值为什么必须传指针 + Elem()?
  4. 泛型约束 int | float64~int | ~float64 的区别?
  5. 什么时候该用接口而不是泛型?
  6. 下面为何 panic?怎么修?
x := 10
reflect.ValueOf(x).SetInt(20)

卡住就问。答案自查 02-语言特性/04-接口与反射.md05-泛型.md