Go反射只能获取导出方法,需用reflect.TypeOf(&struct{})获取指针类型以包含指针接收者方法,未导出方法不可见,判断依据是reflect.Method.PkgPath为空字符串。

用 reflect.TypeOf 获取结构体的可导出方法列表
Go 的反射不能直接列出所有方法(包括未导出方法),reflect.Value.Methods() 和 reflect.Type.Methods() 都只返回**已导出(首字母大写)且可被外部包调用的方法**。这是 Go 的语言设计限制,不是反射 API 的 bug。
实际使用中,绝大多数场景只需要导出方法 —— 比如做通用序列化、RPC 方法发现、测试桩生成等。关键在于:必须传入结构体类型的指针或值的 reflect.Type,而不是接口类型。
- 传入
reflect.TypeOf(&MyStruct{})→ 得到*MyStruct类型,能正确获取其指针接收者方法 - 传入
reflect.TypeOf(MyStruct{})→ 得到MyStruct类型,只能获取值接收者方法 - 传入
reflect.TypeOf(interface{}(MyStruct{}))→ 可能丢失底层类型信息,方法列表为空
reflect.Type.Method(i) 返回的是 reflect.Method,不是函数
reflect.Method 是一个结构体,包含 Name、Type、PkgPath(为空表示导出)、Func(reflect.Value 类型的函数封装)等字段。它本身不是可调用的函数,只是描述信息。
如果想动态调用某个方法,需要先用 reflect.ValueOf(&instance).MethodByName(name) 获取可调用的 reflect.Value,再用 .Call() 执行。注意参数必须是 []reflect.Value,且类型、数量要严格匹配。
立即学习“go语言免费学习笔记(深入)”;
type User struct{}
func (u User) GetName() string { return "Alice" }
func (u *User) SetName(n string) { /* ... */ }
u := User{}
t := reflect.TypeOf(u)
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("Name: %s, PkgPath: %q\n", m.Name, m.PkgPath) // GetName, ""
}
// 注意:SetName 不会出现在这里,因为它是 *User 的方法,而 u 是 User 类型
获取指针接收者方法必须用 *T 类型的 reflect.Type
Go 中值接收者和指针接收者属于不同方法集。结构体值 T 的方法集只包含值接收者方法;而 *T 的方法集包含值 + 指针接收者方法。所以要完整覆盖,应基于指针类型反射:
-
reflect.TypeOf(&MyStruct{}).Elem()先取指针再取元素类型,等价于MyStruct,但此时NumMethod()仍只返回值接收者方法 - 正确做法是直接用
reflect.TypeOf(&MyStruct{}),然后调用其NumMethod()—— 此时得到的是*MyStruct类型的方法数,包含所有指针接收者方法 - 若需同时获取值和指针接收者方法,需分别对
MyStruct{}和&MyStruct{}做反射,并去重(按方法名)
u := &User{}
t := reflect.TypeOf(u) // *User
fmt.Println(t.NumMethod()) // 2:GetName(也属于 *User 方法集) + SetName
无法获取未导出方法,PkgPath 字段是唯一判断依据
Go 反射明确禁止访问未导出成员,这是安全边界。尝试通过 reflect.Value.FieldByName 或 MethodByName 访问小写方法名会返回零值且无 panic,但 IsValid() 为 false。
判断是否导出的唯一可靠方式是检查 reflect.Method.PkgPath:为空字符串 "" 表示导出;非空(如 "myapp/internal")表示未导出,不可跨包访问,反射也不暴露。
有些工具(如 go:generate + ast 包)会绕过反射,直接解析源码 AST 来提取全部方法定义 —— 这是唯一能拿到未导出方法的途径,但代价是失去运行时灵活性,且依赖源文件存在。
别在运行时试图 hack 未导出方法,Go 不支持,也不该支持。










