Go 反射
反射是在运行时检查类型和操作值的能力。Go 的 reflect 包可以读取结构体字段、标签和方法,也可以在满足类型与可设置条件时修改值或调用函数。序列化、配置绑定等通用代码因此可以处理事先没有写死的具体类型。
反射没有改变 Go 的值传递和类型规则,只是把一部分检查放到了运行时。读懂反射代码,需要同时回答:当前拿到的是什么类型,操作的是哪个值,这个值是否允许执行相应操作。本文以 Go 1.22 为基线。
1. 面试核心
先记住这几条,再读正文reflect.Type描述类型,reflect.Value表示具体值及可执行操作。反射可以从接口中的动态类型和值进入,再通过Interface取回普通值;它依赖运行时类型信息,不是把源代码重新解析一遍。卡 · 类型、种类与值Type表示完整类型,Kind只表示种类。type Count int与int类型不同,但种类都是reflect.Int;TypeOf看到的是接口中的动态类型。卡 · 类型、种类与值- 可寻址不等于可设置。通过非 nil 指针的
Elem可以取得所指变量,但未导出字段本身仍不能直接Set;CanInterface则检查能否调用Interface。卡 · 指针与 Elem - 无效的
reflect.Value、某类型的零值和 nil 不是同一件事。先检查IsValid,再按种类选择IsNil、Elem等操作,避免对缺失值执行不适用的方法。卡 · 无效值、零值与 nil - 结构体标签是类型上的字符串元数据,标签的行为由使用它的库解释。
Get不能区分键缺失与显式空字符串,需要这个区别时使用Lookup。卡 · 结构体字段与标签 - 反射查找方法仍受方法集约束。
Value.MethodByName得到的方法值已经绑定接收者,Call只传普通参数,并检查数量和类型是否匹配。卡 · 动态调用与方法集 - 反射适合运行时才确定结构的通用边界;类型明确时优先使用普通代码、接口或泛型。性能要区分元数据查找、值操作与动态调用,并以具体测量判断,不能给 Go 与 Java 反射排一个固定快慢顺序。卡 · 使用场景与性能判断
CanSet只说明当前 Value 能否被直接设置,不代表它关联的数据都不可修改。切片元素、map 内容需要分别按自己的操作规则判断。卡 · CanSet 为 false,为什么仍能修改切片元素和 map 内容?- 可以转换不等于转换没有数值损失。
Set要求可赋值,Convert执行显式转换规则;写入窄整数前,可用OverflowInt等方法检查范围。卡 · 反射会自动完成数值转换和溢出检查吗?
2. 类型、种类与值
reflect.Type 是类型信息,可以查询名称、种类、字段、方法和元素类型。reflect.Value 表示一个具体值,除了查询类型,还提供读取、修改和调用等操作。值能够执行哪些操作,取决于它的类型及取得方式。
package main
import (
"fmt"
"reflect"
)
type Count int
func main() {
count := Count(3)
typ := reflect.TypeOf(count)
value := reflect.ValueOf(count)
fmt.Println(typ, typ.Name(), typ.Kind()) // main.Count Count int
fmt.Println(typ == reflect.TypeOf(int(3))) // false
fmt.Println(value.Int()) // 3
restored := value.Interface().(Count)
fmt.Printf("%T %v\n", restored, restored) // main.Count 3
}
Kind 是类型所属的种类,例如整数、结构体、切片和指针。Count 的完整类型是 main.Count,种类却是 reflect.Int。所以可以用 Int() 读取它;Int() 返回 int64,并不表示原值的类型变成了 int64。
Interface() 把当前值作为 any 返回。随后断言为 Count,能取回具体类型的值;断言为 int 则会失败,因为命名类型的身份仍然存在。类型断言的规则见 Go 接口。
需要判断两个类型是否相同时,可以直接比较两个 reflect.Type。不要仅比较 Name()、String() 或 Kind():不同包可能声明同名类型,种类相同也不代表类型相同。
2.1 接口的动态类型与接口变量本身
TypeOf 和 ValueOf 接收 any。传入接口变量时,反射看到的是其中保存的具体动态类型和值,不会在外面再套一层原接口类型。
package main
import (
"fmt"
"io"
"reflect"
"strings"
)
func main() {
var reader io.Reader = strings.NewReader("Go")
fmt.Println(reflect.TypeOf(reader)) // *strings.Reader
slot := reflect.ValueOf(&reader).Elem()
fmt.Println(slot.Kind() == reflect.Interface) // true
fmt.Println(slot.Type() == reflect.TypeFor[io.Reader]()) // true
fmt.Println(slot.Elem().Type() == reflect.TypeOf(reader)) // true
}
reflect.ValueOf(reader) 查看 *strings.Reader 这个具体值。reflect.ValueOf(&reader).Elem() 则表示 reader 这个接口变量本身,所以 slot.Type() 是 io.Reader;再调用一次 slot.Elem(),才取得接口当前保存的动态值。
reflect.TypeFor[T]() 从 Go 1.22 起提供,用来取得类型实参 T 的类型信息。这里也可以写 reflect.TypeOf((*io.Reader)(nil)).Elem():先取得接口指针类型,再取得其元素类型。它不需要构造一个实际的读取器。
运行时会保留类型相关的元数据,反射按这些元数据解释值。理解这条链路不需要依赖接口内部的具体内存布局,也不能把某个版本的布局当成通用 API 契约。
3. 可寻址、可设置与导出限制
可寻址表示可以通过 Addr 取得该值的指针,由 CanAddr 判断。可设置表示允许通过 Set 或相应的类型设置方法修改这个 Value 所表示的值,由 CanSet 判断。可设置首先要求可寻址,还受字段导出状态等限制,需要检查实际要修改的 Value。
3.1 指针与 Elem
把普通变量传给 ValueOf,遵守值传递规则。ValueOf 得到的值不能直接作为调用方原变量来修改;要修改变量,可以传入指针,再用 Elem 取得所指变量。
package main
import (
"fmt"
"reflect"
)
func main() {
number := 10
copied := reflect.ValueOf(number)
pointer := reflect.ValueOf(&number)
target := pointer.Elem()
fmt.Println(copied.CanAddr(), copied.CanSet()) // false false
fmt.Println(pointer.CanAddr(), pointer.CanSet()) // false false
fmt.Println(target.CanAddr(), target.CanSet()) // true true
target.SetInt(20)
fmt.Println(number) // 20
// copied.SetInt(20) // 运行时 panic:copied 不可设置
}
pointer 本身保存的是传入的指针值,不能直接把它当作调用方的某个指针变量来重新赋值。pointer.Elem() 才表示 number 所在的位置,所以 target.SetInt 可以改到 number。
Elem 不是通用的“去掉任意外壳”操作。Value.Elem 用于指针或接口值;指针是 nil、接口没有动态值时,它返回无效 Value。若直接对整数调用 Elem,会在运行时 panic。
3.2 未导出字段与 Interface
结构体本身可寻址,不表示每个字段都可设置。普通反射 API 会保留未导出字段的限制,即使反射调用与结构体声明位于同一个包,也不能借 Set 修改该字段。
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
age int
}
func main() {
user := User{Name: "Ada", age: 20}
value := reflect.ValueOf(&user).Elem()
name := value.FieldByName("Name")
age := value.FieldByName("age")
fmt.Println(name.CanAddr(), name.CanSet(), name.CanInterface()) // true true true
fmt.Println(age.CanAddr(), age.CanSet(), age.CanInterface()) // true false false
name.SetString("Lin")
fmt.Println(user.Name) // Lin
// age.SetInt(21) // 运行时 panic:未导出字段不可设置
// age.Interface() // 运行时 panic:不能把该字段作为接口取出
}
CanInterface 与 CanSet 回答不同的问题。一个通过 ValueOf(3) 得到的值不能设置,却可以调用 Interface。相反,未导出字段即使可寻址,也不能通过 Interface 取出。
未导出的嵌入字段与普通未导出字段还有一点区别。例如,type inner struct { X int } 嵌入 type Outer struct { inner } 后,从可寻址的 Outer 取得 inner 时不能直接设置这个嵌入字段,但继续取得导出字段 X 后可以设置。普通未导出命名字段的限制则会传递到它的子字段。因此,不能只根据父字段的 CanSet 推断子字段是否可设置。
字段查找也可能失败。FieldByName 返回无效 Value 时,应先识别缺失,再决定报错或采用默认值;不能直接接着调用 SetString。
4. 无效值、零值与 nil
无效 Value表示没有可操作的值,例如 reflect.Value{}、ValueOf(nil) 或不存在的字段查询结果。某类型的零值仍有确定的类型,例如 int(0);nil 指针也有指针类型。
package main
import (
"fmt"
"reflect"
)
func main() {
missing := reflect.ValueOf(nil)
fmt.Println(reflect.TypeOf(nil) == nil) // true
fmt.Println(missing.IsValid(), missing.Kind()) // false invalid
var pointer *int
value := reflect.ValueOf(pointer)
fmt.Println(value.IsValid(), value.IsNil()) // true true
fmt.Println(value.Elem().IsValid()) // false
zero := reflect.Zero(reflect.TypeOf(0))
fmt.Println(zero.IsValid(), zero.IsZero(), zero.CanSet()) // true true false
// missing.IsNil() // 运行时 panic:无效 Value 不适用 IsNil
// zero.IsNil() // 运行时 panic:int 不适用 IsNil
}
IsValid 先回答是否存在值,IsZero 再回答它是否为所属类型的零值。reflect.Zero 创建对应类型的零值,但该结果本身不可设置;如果需要新建可修改的存储,可以使用 reflect.New(typ) 取得指针,再操作它的 Elem。
IsNil 用于允许 nil 的相应种类,例如指针、接口、切片、map、函数和 channel,不能对所有输入无条件调用。Type、Interface、IsNil、IsZero 等操作对无效 Value 会 panic,因此处理任意输入时,检查顺序本身就是接口契约的一部分。
nil 接口与带类型 nil 的接口仍遵守普通 Go 规则。var value any = (*int)(nil) 中的 value 不等于 nil,但 ValueOf(value) 得到一个有效的 nil 指针 Value。相关区别见 Go 接口。
5. 结构体字段与标签
结构体标签是附在字段声明上的字符串元数据。reflect.Type 可以取得字段的名称、类型、索引和标签;具体库再按照自己的规则解释标签。标签不会自己执行序列化,也不会让未导出字段变成导出字段。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Alias string `json:""`
Hidden string `json:"-"`
secret string `json:"secret"`
}
func main() {
typ := reflect.TypeOf(User{})
for _, name := range []string{"Name", "Alias", "Hidden", "secret"} {
field, _ := typ.FieldByName(name)
tag, present := field.Tag.Lookup("json")
fmt.Printf("%s %q %t %t\n", name, tag, present, field.IsExported())
}
field, _ := typ.FieldByName("Alias")
_, present := field.Tag.Lookup("db")
fmt.Println(present) // false
data, err := json.Marshal(User{Name: "Lin", Alias: "L", Hidden: "h", secret: "s"})
if err != nil {
panic(err)
}
fmt.Println(string(data)) // {"name":"Lin","Alias":"L"}
}
前四行分别输出字段名、json 标签值、该标签键是否存在,以及字段是否导出。Alias 的标签值是空字符串,但 Lookup("json") 返回的 present 为 true;Lookup("db") 则为 false。Get 对这两种情况都返回空字符串,无法区分。
encoding/json 把 Name 输出为 name,对空名称的 Alias 使用字段名,跳过标为 - 的 Hidden。secret 虽然写了标签,仍然是未导出字段,所以不会因此进入这个对象的 JSON 输出。
通用字段处理还需要决定如何对待嵌入字段、同名冲突和 nil 的嵌入指针。Type.FieldByName 查询的是字段元数据,Value.FieldByName 访问的是当前对象的值;后者在经过 nil 嵌入指针时可能失败。不能只确认“类型上存在字段”,就假设所有实例都能直接访问到它。
标签应符合约定的 key:"value" 格式。格式不符合约定时,Get、Lookup 的结果没有保证;可以使用 go vet 检查常见标签问题,但仍需按所用库的规则处理字段。
6. 动态调用与方法集
Value.Call 可以在运行时调用函数,输入与输出都用 reflect.Value 表示。函数种类、参数数量和可赋值关系不匹配时,会 panic;函数内部发生的 panic 也不会因为使用反射而自动变成错误返回值。
package main
import (
"fmt"
"reflect"
)
type User struct{ Name string }
func (u *User) Rename(name string) { u.Name = name }
func (u User) Hello(prefix string) string { return prefix + u.Name }
func main() {
user := User{Name: "Ada"}
value := reflect.ValueOf(&user).Elem()
fmt.Println(value.CanAddr(), value.MethodByName("Rename").IsValid()) // true false
pointer := reflect.ValueOf(&user)
rename := pointer.MethodByName("Rename")
rename.Call([]reflect.Value{reflect.ValueOf("Lin")})
result := pointer.MethodByName("Hello").Call([]reflect.Value{reflect.ValueOf("hello ")})
fmt.Println(result[0].String()) // hello Lin
}
User 的方法集没有指针接收者的 Rename。即使 value.CanAddr() 为 true,value.MethodByName("Rename") 也不会自动替我们取地址。使用指针 Value,或者在可寻址时显式使用 value.Addr(),才能按 *User 的方法集查找。方法集与普通调用简写的区别见 Go 结构体与方法集。
Value.MethodByName 返回的是已绑定接收者的方法值,所以调用 rename.Call 时只传 name,不再传 user。Type.MethodByName 返回的则是方法元数据;对非接口类型,其 Method.Func 对应的函数把接收者作为第一个参数,不能混用这两种签名。
方法存在也不表示任意输入都能安全执行。还需要确认参数可赋值、指针接收者是否允许 nil,以及方法本身的业务前置条件。能静态确定接口时,直接调用接口方法通常更容易让编译器检查这些关系。
7. 使用场景与性能判断
反射适合编写需要在运行时处理不同数据形状的通用边界。例如,配置绑定器读取结构体字段和标签,把字符串转换后写入目标字段;序列化器根据值的类型选择输出方式。这些场景依赖的是结构信息,不只是一组统一方法。
| 需求 | 可以采用的方式 |
|---|---|
| 固定业务类型的字段读取与更新 | 普通字段访问和函数调用 |
| 依赖一组稳定行为,例如读取、关闭 | 接口 |
| 同一算法适用于多种类型,并保留输入输出类型关系 | 泛型 |
| 运行时才知道字段结构、标签或完整调用签名 | 反射,并明确输入与失败契约 |
| 类型集合在构建时已知,希望生成重复的映射代码 | 代码生成 |
反射的成本来自具体操作:按名称查找字段或方法、检查类型、构造调用参数与结果,以及可能产生的分配。TypeOf、缓存后的字段访问和 Call 做的工作不同,不能把它们都归为同一种固定开销。
需要优化时,可以先用性能分析确定热点,再把重复的字段元数据解析移到初始化阶段,按 reflect.Type 缓存结果。缓存的是类型对应的字段规则,不应把某一个对象的可变 Value 当作所有实例的目标。缓存命中后的路径仍要保留实际值所需的 nil、范围和可设置检查。
Go 和 Java 的反射性能也不能脱离任务直接比较。应固定相同的输入、操作和结果消费方式,分别说明 Go、JDK 的版本与运行条件;Java 还要考虑预热后的运行状态。比较查找与调用、是否缓存、耗时和分配,才有可解释的结果。仅凭“Go 编译成机器码”或“Java 有运行时优化”,不能推出某种反射始终更快。
8. 常见问题
8.1 CanSet 为 false,为什么仍能修改切片元素和 map 内容?
CanSet 判断的是能否直接替换这个 Value 所表示的值,不是在判断所有能通过它访问的数据是否只读。切片元素有自己的可寻址性,map 则通过专门的操作更新内容。
package main
import (
"fmt"
"reflect"
)
func main() {
values := []int{1}
slice := reflect.ValueOf(values)
fmt.Println(slice.CanSet(), slice.Index(0).CanSet()) // false true
slice.Index(0).SetInt(9)
fmt.Println(values) // [9]
counts := map[string]int{"a": 1}
mapping := reflect.ValueOf(counts)
key := reflect.ValueOf("a")
entry := mapping.MapIndex(key)
fmt.Println(mapping.CanSet(), entry.CanAddr(), entry.CanSet()) // false false false
mapping.SetMapIndex(key, reflect.ValueOf(2))
fmt.Println(counts["a"]) // 2
mapping.SetMapIndex(key, reflect.Value{})
fmt.Println(mapping.MapIndex(key).IsValid()) // false
}
slice 不能直接替换调用方的切片变量,但 slice.Index(0) 对应底层数组中的元素,可以修改。MapIndex 取出的元素本身不可寻址,更新 map 要使用 SetMapIndex。
SetMapIndex 的第二个参数为无效 reflect.Value{} 时表示删除键。传入 reflect.Zero(mapping.Type().Elem()) 则表示把键对应的值设为元素类型的零值,两者不同。向 nil map 写入值仍会 panic,反射不会自动初始化 map。
8.2 反射会自动完成数值转换和溢出检查吗?
不会。Set 要求输入值可以赋给目标类型,不能把 int64 自动当成 int8。需要转换时可以显式调用 Convert,但 Go 的数值转换允许发生截断,反射也沿用这些规则。
package main
import (
"fmt"
"reflect"
)
func main() {
var number int8
target := reflect.ValueOf(&number).Elem()
fmt.Println(target.OverflowInt(128)) // true
target.SetInt(128) // 特意不拦截一次,观察超出 int8 范围的结果
fmt.Println(number) // -128
source := reflect.ValueOf(int64(7))
fmt.Println(source.Type().AssignableTo(target.Type())) // false
fmt.Println(source.CanConvert(target.Type())) // true
target.Set(source.Convert(target.Type()))
fmt.Println(number) // 7
}
SetInt 按目标整数类型写入,不会因为 128 超出 int8 范围而主动报错。绑定外部输入时,应先用 OverflowInt 判断;无符号整数和浮点数也有各自的范围检查方法。
CanConvert 检查这次转换能否执行而不因转换规则 panic,不保证数值无损。它还会考虑某些值相关条件,例如切片转数组时的长度;只检查类型层面的 ConvertibleTo,不能替代所有值检查。
8.3 可以用 unsafe 代替反射检查来提升性能吗?
unsafe 可以越过部分类型安全限制,但它不会自动保留普通反射的输入与生命周期保证。使用它需要独立说明内存布局、对齐、对象存活和访问同步等条件,不能把“程序暂时能运行”作为正确性证明。
尤其是 uintptr 只是整数,不会因为保存了某个地址就让垃圾回收器把它当作引用。不能把地址长期保存成整数,再假定之后随时转回指针都有效。具体转换还必须遵守 unsafe 文档列出的合法形式。
在面试准备中,应先掌握普通反射的类型和可设置规则。只有目标岗位或项目明确涉及底层互操作时,再按具体用途分析 unsafe;它不是处理字段不存在、类型不匹配或未导出字段的通用补救措施。
9 · 面试题卡片
本页题目与参考回答
9.1 Go 反射的基本机制是什么,哪些场景适合使用?
考察重点:接口动态信息、类型元数据、运行时操作与适用边界。
相关内容:类型、种类与值、接口的动态类型与接口变量本身、使用场景与性能判断
参考回答
Go 反射通过运行时类型信息检查类型和操作值,常用入口是 reflect.TypeOf 和 reflect.ValueOf。 它们接收接口值,取得具体动态类型和值,再按类型、导出状态和可设置条件执行操作;允许时可以用 Interface 取回普通值。
序列化、配置绑定等需要处理不同结构和字段标签的通用代码适合考虑反射。类型明确的业务逻辑可以直接访问字段;统一行为用接口,同类算法与类型关系用泛型。反射中的类型或调用错误可能在运行时才暴露,需要明确检查与失败处理。
9.2 reflect.Type、reflect.Value 和 Kind 有什么区别,TypeOf 接口变量会得到什么?
考察重点:完整类型、种类、动态类型与接口变量。
相关内容:类型、种类与值、接口的动态类型与接口变量本身
参考回答
Type 描述完整类型,Value 表示具体值及其操作状态,Kind 只表示类型种类。 type Count int 得到的类型不同于 int,但二者的 Kind 都是 reflect.Int。
TypeOf(reader) 得到接口中具体值的动态类型,例如 *strings.Reader。若要取得接口变量本身,可以反射 &reader 后调用 Elem;若只需要接口类型的信息,Go 1.22 可以使用 TypeFor[io.Reader]()。
9.3 为什么 ValueOf(x) 不能直接修改 x,CanAddr 和 CanSet 是否相同?
考察重点:值传递、指针解引用、可设置性与导出限制。
相关内容:指针与 Elem、未导出字段与 Interface
参考回答
ValueOf(x) 得到传入的值,不能把它当作调用方变量直接设置;修改原变量通常需要传入指针并操作 Elem。
CanAddr 表示可以取得指针,CanSet 的要求更严格,还要满足导出限制。例如可寻址结构体的未导出字段可以 CanAddr() == true,却 CanSet() == false。嵌入字段中的导出子字段需要单独判断。CanInterface 则判断能否调用 Interface,不是可设置性的同义词。
9.4 ValueOf(nil)、nil 指针的 Value 和 reflect.Zero 有什么区别?
考察重点:无效值、类型零值、nil 与检查顺序。
相关内容:无效值、零值与 nil
参考回答
ValueOf(nil) 是无效 Value;nil 指针的 Value 有指针类型,仍是有效值;reflect.Zero(typ) 创建的是 typ 对应的有效零值。
无效 Value 的 IsValid 为 false,对它调用 IsNil、Type 等方法会 panic。nil 指针可以 IsNil,但 Elem 返回无效 Value。reflect.Zero 的结果不可设置;需要新建可修改存储时,可以使用 reflect.New(typ).Elem()。
9.5 结构体标签由谁解释,Get 与 Lookup 有什么区别?
考察重点:元数据与库行为、标签键是否存在、字段可见性。
相关内容:结构体字段与标签
参考回答
标签本身是字符串元数据,行为由读取它的库解释。 例如 encoding/json 解释 json 标签中的名称和选项;标签不会自动修改字段,也不会让普通未导出字段参与 JSON 输出。
Get 在键不存在或值明确为空时都返回空字符串。Lookup 还返回键是否存在,可以区分两种情况。处理嵌入字段时,还要区分类型上存在的字段与当前实例能否经过非 nil 路径访问到该字段。
9.6 反射调用方法时要传接收者吗,可寻址的值会自动找到指针方法吗?
考察重点:绑定的方法值、方法元数据、方法集和运行时签名检查。
相关内容:动态调用与方法集
参考回答
Value.MethodByName 得到的方法值已经绑定接收者,Call 不再传接收者。 对非接口类型,Type.MethodByName 得到的 Method.Func 则把接收者作为第一个参数。
反射查找遵守当前类型的方法集,不因为 Value 可寻址就自动查找指针类型的方法。需要指针方法时,显式取得指针 Value;调用前还要检查方法存在、参数数量与可赋值关系。
9.7 Go 和 Java 的反射性能如何比较,反射代码可以怎样优化?
考察重点:具体操作、缓存、运行条件、测量与类型化替代方案。
相关内容:使用场景与性能判断、unsafe 与反射
参考回答
不能给出脱离操作、版本和测量条件的固定快慢顺序。 需要区分类型查询、按名查找字段、读取或写入值、动态调用,以及是否已经缓存元数据;跨语言还要统一输入与结果消费方式,并说明各自编译、运行和预热条件。
先确定热点,再考虑按类型缓存字段规则、减少重复查找和参数构造。类型与行为能够在编译期明确时,也可以使用普通代码、接口、泛型或代码生成。使用 unsafe 还会引入额外正确性条件,不能单凭绕过检查就认定是有效优化。
9.8 CanSet 为 false 是否表示完全不能修改数据,map 元素怎样更新或删除?
考察重点:变量与关联数据、切片元素、map 更新和删除。
相关内容:集合内容与可设置性
参考回答
不是,CanSet 只约束当前 Value 的直接设置。 ValueOf(slice) 本身不可设置,但其元素通常可以设置;ValueOf(map) 也可通过 SetMapIndex 更新内容。
MapIndex 返回的元素不可寻址,不能直接对它 Set。SetMapIndex(key, reflect.Value{}) 删除键,传入元素类型的有效零值则是把值设为零。向 nil map 写入值仍会 panic。
9.9 CanConvert 为 true 是否说明转换无损,SetInt 会拒绝超出范围的整数吗?
考察重点:可赋值与可转换、值相关条件、整数截断与范围验证。
相关内容:数值转换与范围检查
参考回答
都不能这样判断。CanConvert 检查转换能否执行,不保证数值无损;SetInt 也不会自动拒绝整数截断。 例如把 128 写入 int8,结果可以变成 -128。
Set 要求类型可赋值,需要转换时显式调用 Convert。处理外部数值应先按目标类型检查范围,例如使用 OverflowInt;切片转数组等操作还依赖实际长度,不能只检查类型上的 ConvertibleTo。
10. 参考资料
- Go 1.22.12 reflect 包文档
- Go 1.22.12 reflect.Value 实现
- Go 1.22.12 reflect.Type 与结构体标签
- Go 官方博客:The Laws of Reflection
- Go 1.22.12 encoding/json 字段规则
- Go 1.22 Release Notes:reflect.TypeFor
- Go 1.22.12 unsafe 包文档
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明