Go 类型断言
断言语法、comma-ok、type switch、以及接口的运行时类型。
两种形式
var x any = "hello"
// 形式一:失败 panic
s := x.(string)
fmt.Println(s)
// 形式二:comma-ok,失败不 panic
n, ok := x.(int)
fmt.Println(n, ok)
// fmt.Println(x.(int)) // panic: interface conversionhello
0 false什么时候需要断言
// 从 map[string]any 里取值(JSON 解析常见)
var data any = map[string]any{
"name": "tom",
"age": 30,
}
m := data.(map[string]any)
if name, ok := m["name"].(string); ok {
fmt.Println("名字", name)
}
// 数字在 JSON 里默认是 float64
if age, ok := m["age"].(float64); ok {
fmt.Println("年龄", int(age))
}名字 tom
年龄 30encoding/json decodes numbers as float64, never int, so .(int) always fails — the classic bug with dynamic JSON. Decode into a struct, or enable json.Decoder.UseNumber().
type switch
func kind(v any) string {
switch x := v.(type) {
case nil:
return "nil"
case int, int64:
return "整数"
case float64:
return fmt.Sprintf("浮点 %.1f", x)
case string:
return "字符串长度 " + strconv.Itoa(len(x))
case []any:
return fmt.Sprintf("数组 %d 个元素", len(x))
default:
return fmt.Sprintf("未知 %T", x)
}
}
fmt.Println(kind(1))
fmt.Println(kind(3.14))
fmt.Println(kind("go"))
fmt.Println(kind([]any{1, 2}))整数
浮点 3.1
字符串长度 2
数组 2 个元素Inside switch x := v.(type), x has a different type per case: in a multi-type case (int, int64) it stays any; in a single-type case it is that concrete type.
断言接口
type Stringer interface{ String() string }
func toString(v any) string {
// 断言的是「它是否实现了某接口」,不是具体类型
if s, ok := v.(Stringer); ok {
return s.String()
}
return fmt.Sprint(v)
}
type Temp float64
func (t Temp) String() string { return fmt.Sprintf("%.1f°C", float64(t)) }
fmt.Println(toString(Temp(36.6)))
fmt.Println(toString(42))36.6°C
42This is the most valuable use: runtime capability probing. io.Copy asserts WriterTo/ReaderFrom and takes the fast path when available, else the generic buffered copy.
断言 vs 反射
| Aspect | Type assertion | reflect |
|---|---|---|
| 适用 | 已知几个候选类型 | 完全未知的任意类型 |
| 性能 | 快(一次比较) | 慢(10~100 倍) |
| 可读性 | 好 | 差 |
| 典型场景 | JSON 解析、插件钩子 | ORM、序列化框架 |
断言失败的排查
// 别写这种,出错时完全不知道是什么类型
v := data.(map[string]any)
// 写成这样,报错信息带上实际类型
m, ok := data.(map[string]any)
if !ok {
return fmt.Errorf("期望 map[string]any,实际 %T", data)
}两种形式
var x any = "hello"
// 形式一:失败 panic
s := x.(string)
fmt.Println(s)
// 形式二:comma-ok,失败不 panic
n, ok := x.(int)
fmt.Println(n, ok)
// fmt.Println(x.(int)) // panic: interface conversionhello
0 false什么时候需要断言
// 从 map[string]any 里取值(JSON 解析常见)
var data any = map[string]any{
"name": "tom",
"age": 30,
}
m := data.(map[string]any)
if name, ok := m["name"].(string); ok {
fmt.Println("名字", name)
}
// 数字在 JSON 里默认是 float64
if age, ok := m["age"].(float64); ok {
fmt.Println("年龄", int(age))
}名字 tom
年龄 30encoding/json 把数字解成 float64 而不是 int。直接 .(int) 一定失败——这是处理动态 JSON 时最常见的 bug。要精确控制就解到结构体,或加 json.Decoder.UseNumber()。
type switch
func kind(v any) string {
switch x := v.(type) {
case nil:
return "nil"
case int, int64:
return "整数"
case float64:
return fmt.Sprintf("浮点 %.1f", x)
case string:
return "字符串长度 " + strconv.Itoa(len(x))
case []any:
return fmt.Sprintf("数组 %d 个元素", len(x))
default:
return fmt.Sprintf("未知 %T", x)
}
}
fmt.Println(kind(1))
fmt.Println(kind(3.14))
fmt.Println(kind("go"))
fmt.Println(kind([]any{1, 2}))整数
浮点 3.1
字符串长度 2
数组 2 个元素switch x := v.(type) 里 x 的类型在每个 case 分支中不同:多类型 case(int, int64)里 x 仍是 any,单类型 case 里 x 就是那个具体类型。
断言接口
type Stringer interface{ String() string }
func toString(v any) string {
// 断言的是「它是否实现了某接口」,不是具体类型
if s, ok := v.(Stringer); ok {
return s.String()
}
return fmt.Sprint(v)
}
type Temp float64
func (t Temp) String() string { return fmt.Sprintf("%.1f°C", float64(t)) }
fmt.Println(toString(Temp(36.6)))
fmt.Println(toString(42))36.6°C
42这是断言最有价值的用法:运行时探测能力。标准库的 io.Copy 就是先断言 WriterTo / ReaderFrom,有就用优化路径,没有就退回通用缓冲复制。
断言 vs 反射
| 对比项 | 类型断言 | reflect |
|---|---|---|
| 适用 | 已知几个候选类型 | 完全未知的任意类型 |
| 性能 | 快(一次比较) | 慢(10~100 倍) |
| 可读性 | 好 | 差 |
| 典型场景 | JSON 解析、插件钩子 | ORM、序列化框架 |
断言失败的排查
// 别写这种,出错时完全不知道是什么类型
v := data.(map[string]any)
// 写成这样,报错信息带上实际类型
m, ok := data.(map[string]any)
if !ok {
return fmt.Errorf("期望 map[string]any,实际 %T", data)
}