Skip to content

接口与反射

eface/iface 底层、类型断言、空接口、reflect 代价。

Updated View as Markdown
For humans

接口与反射

Go 的接口是隐式实现(鸭子类型:结构满足方法集就自动实现接口)。面试主线:接口底层结构、断言、空接口、反射为什么慢。

接口底层:eface 与 iface

iface 带方法表, eface 只有类型

  • eface(空接口 interface{} / any_type(类型信息)+ data(数据指针)
  • iface(有方法的接口)tab(接口类型 + 具体类型的方法表)+ data
  • 接口值 = 类型 + 数据:一个接口变量是否 nil,要看类型是否为 nil(见下)

nil 接口陷阱(必考)

var p *int = nil
var i interface{} = p   // i != nil! 因为 _type 不是 nil
if i == nil {           // false
}

判断接口是否为 nil 必须用反射(reflect.ValueOf(i).IsNil())或类型断言。

类型断言

var i any = "hello"

s := i.(string)         // 断言成功: s = "hello"
n := i.(int)            // 失败: panic
s, ok := i.(string)     // 安全形式: ok = false 不 panic
  • 两种形式:单返回值(失败 panic)、双返回值(ok 模式,推荐)
  • type switch:多类型分支断言
  • 断言本质:检查接口里的动态类型是否匹配

空接口

  • interface{} / any:不约束方法,任何类型都满足
  • 用途:通用容器(map[string]any)、函数入参(fmt.Println 接收 any)
  • 代价:类型信息丢失,取值要断言;装箱(堆分配)有开销
  • 工程原则:空接口是兜底手段,能明确类型就别用 any(失去编译期检查)

反射:reflect

v := reflect.ValueOf(x)
t := v.Type()          // 类型信息
v.Kind()               // 基础种类(Struct/Slice/...)
v.Field(i).Set(...)    // 运行时修改(需可寻址)

反射的代价(考点):

代价 原因
运行时类型检查 + 动态分发,比直接调用慢一个量级
逃逸 反射操作的值逃逸到堆,增加 GC 压力
不安全 编译期检查失效,错误延迟到运行时

反射的正确用途:序列化(encoding/json)、ORM(gorm 标签)、依赖注入、测试断言。热点路径别用反射(可以生成代码替代:stringer、gen 工具)。

面试追问

  1. eface 和 iface? eface 是空接口(_type + data),iface 是有方法接口(tab 方法表 + data)
  2. nil 接口的陷阱? 带类型的 nil 赋给接口后接口不为 nil(类型非 nil)。判空用反射或断言
  3. 类型断言两种形式? 单返回值失败 panic,双返回值(ok)安全。生产用 ok 形式
  4. 反射为什么慢? 运行时类型检查 + 动态分发 + 逃逸到堆。热点路径避免
  5. 空接口什么时候用? 通用容器/入参。能明确类型就别用,失去编译期安全
Navigation

Type to search…

↑↓ navigate↵ selectEsc close