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 雖然有泛型了,但有些場景你還是得靠反射,比如:

反射 + 泛型,有時候能讓框架代碼寫得非常優雅,比如這樣:

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