golang 反射 new?
今天來聊一個在 Golang 裏新手容易踩坑但高級用法又很關鍵的點:反射 + reflect.New
。看上去很底層,實際在做框架開發、泛型模擬、動態構造對象的時候,全靠它撐場子!
我前段時間折騰一個插件系統(Go 寫的),要從字符串配置動態構造 struct 對象實例,就必須用 reflect.New
。這塊調試了好一陣,今天把經驗和注意事項總結一下,走一波實戰向的乾貨分享。
1)reflect.New
是個啥?
簡單一句話:創建某個類型的 “指針” 實例。
它的函數簽名是:
func reflect.New(typ reflect.Type) reflect.Value
舉個最基本的例子:
type User struct {
Name string
}
t := reflect.TypeOf(User{}) // 注意:不是指針類型
v := reflect.New(t) // 創建 *User
u := v.Interface().(*User) // 拿到真正的 *User 類型
u.Name = "老鬼"
fmt.Println(u.Name) // 輸出:老鬼
那這裏就有一個關鍵點了👇
2)New
和 NewAt
有啥區別?
其實大部分人只知道 New
,但還有個更猛的叫 reflect.NewAt
,是用來創建指向特定內存地址的對象的:
func reflect.NewAt(typ Type, p unsafe.Pointer) Value
這個比較少用,只有你在底層做內存映射(比如 mmap)、或者二進制解析(我試過的 proto 優化)才用得上,一般業務代碼用 New
就夠了。
3)重點來了:反射 + 實例構造的真實使用場景
我舉幾個我自己遇到的:
3.1)插件系統動態構造對象
配置裏是字符串:
handlers:
- type: MyHandler
- type: AnotherHandler
你需要根據 "MyHandler" 創建對應 struct 的實例,這時候你就得有個註冊表:
var registry = map[string]reflect.Type{
"MyHandler": reflect.TypeOf(MyHandler{}),
}
func CreateInstance(typeName string) interface{} {
typ := registry[typeName]
return reflect.New(typ).Interface()
}
然後就能拿到 *MyHandler
,繼續調用方法啥的。這個套路也用於:
-
配置驅動組件
-
動態數據綁定
-
通用結構體填充
我之前在做 JSON 到 struct 的映射時,用反射批量創建結構體數組,性能直接拉滿。
4)易錯點彙總!
這塊我之前踩過不少坑,幫大家避坑:
❌ 用 reflect.TypeOf(ptr)
來創建對象
reflect.TypeOf(&User{}) // 這是 *User
如果你再 reflect.New()
,你會拿到 **User
,直接爆炸:
t := reflect.TypeOf(&User{}) // t 是 *User
v := reflect.New(t) // v 是 **User
正確做法:
t := reflect.TypeOf(User{}) // t 是 User
v := reflect.New(t) // v 是 *User
❌ 忘了轉 interface{}
reflect.New(t)
返回的是 reflect.Value
,你要 .Interface()
一下才是你想要的東西!
5)結合泛型模擬效果更炸
Go 1.18 雖然有泛型了,但有些場景你還是得靠反射,比如:
-
動態解析任意 struct 到表單
-
JSON 解碼時處理 map[string]interface{}
-
需要把字段名字、tag 等做動態處理
反射 + 泛型,有時候能讓框架代碼寫得非常優雅,比如這樣:
func NewInstance[T any]() *T {
var t T
return reflect.New(reflect.TypeOf(t)).Interface().(*T)
}
這個是個小 trick,模擬 “泛型構造器” 行爲,我用在了我寫的數據庫 ORM 工具裏。
6)最後來個綜合示例
假設我們做一個通用的 Struct 工廠,可以根據類型名字動態創建實例:
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
type Dog struct {
Name string
}
var registry = map[string]reflect.Type{
"Cat": reflect.TypeOf(Cat{}),
"Dog": reflect.TypeOf(Dog{}),
}
func NewByName(name string) interface{} {
t, ok := registry[name]
if !ok {
panic("not registered: " + name)
}
return reflect.New(t).Interface()
}
func main() {
c := NewByName("Cat").(*Cat)
c.Name = "喵喵"
fmt.Println(c)
d := NewByName("Dog").(*Dog)
d.Name = "汪汪"
fmt.Println(d)
}
輸出:
&{喵喵}
&{汪汪}
性能上也還行,我做過 benchmark,對象構造這塊 reflect.New
性能比 new(...) 慢個幾倍,但不是量級差異(不像 method call 那種慢上百倍),在初始化階段影響不大。
最後總結一下:
1)reflect.New(t)
是創建 “指針實例” 的關鍵函數,是反射構造的基礎操作。
2)常用在框架、插件系統、配置驅動型代碼中,跟註冊表模式結合超香。
3)一定記住:傳值類型進去、取出來做 Interface()
再斷言!
這個反射玩法確實不適合業務層日常使用,但如果你想擼點底層工具、做點通用能力,它就是你必須掌握的那一招。
感興趣的朋友可以試試,我會繼續分享一些反射、unsafe、泛型混合玩法,歡迎點關注,不迷路~有問題歡迎交流,我們下篇再見!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dYWAQs31RCR-vpw5LxPvzg