Go 反射:根據類型建立對象-第二部分(複合類型)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。> 這是關於 Golang 中根據類型建立對象系列部落格的第二篇,討論的是建立綜合物件。第一篇在[這裡](https://studygolang.com/articles/12434)![](https://raw.githubusercontent.com/studygolang/gctt-images/master/go-reflect/cover1.png)在前一篇部落格中,我解釋了 go reflect 包 `type` 和 `kind` 的概念。這篇部落格,我將深入探討這些術語。因為相比原始類型,`type` 和 `kind` 對於複合類型來說含義更多。## 類型和種類“類型” 是程式員用來描述程式中資料和函數的中繼資料。`type` 在 Go 的運行時和編譯器中有不同的含義。可以通過一個例子來理解。例如有如下聲明:```govar Version string```提到這個聲明,程式員會說 `Version` 是一個 `string` 類型的變數。考慮下面這個例子,`Version` 是一個複合類型。```gotype VersionType stringvar Version VersionType```在這個例子中,第一行建立了一個新類型 `VersionType`。第二行,`Version` 被定義成一個 `VersionType` 類型的變數。這個類型(VersionType)的類型是 `string`,我們稱之為 `kind`。總結來說就是,`Version` 的 `type` 是 `VersionType`,`Version` 的 `kind` 是 `string`。> 類型是程式員定義的關於資料和函數的中繼資料。種類是編譯器和運行時定義的關於資料和函數的中繼資料。運行時和編譯器根據 `Kind` 來分別給變數和函數分配記憶體或棧空間。## 根據原始種類來建立綜合物件建立 `kind` 為如下值的綜合物件與根據原始種類建立原始對象並沒有什麼不同。```goBoolIntInt8Int16Int32Int64UintUint8Uint16Uint32Uint64UintptrFloat32Float64Complex64Complex128StringUnsafePointer```下面是通過類型(`VersionType`)來建立 `Version` 的例子。因為 `Version` 有原始的 `kind`,故它可以使用零值來建立。```gofunc CreatePrimitiveObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}func extractVersionType(v reflect.Value) (VersionType, error) {if v.Type().String() != "VersionType" { return "", errors.New("invalid input")}return v.String(), nil}// 譯者註:上面代碼似乎有錯誤,改成了下面所示(亦或是故意省略包名首碼)func extractVersionType(v reflect.Value) (string, error) {if v.Type().String() != "main.VersionType" {return "", errors.New("invalid input")}return v.Type().String(), nil}```注意到一個 `Type` 類型的變數的 `String()` 方法將返回 `Type` 的全路徑名稱。例如, 如果 `VersionType` 定義在 `mypkg` 包中,`String()` 返回的值將為 `mypkg.VersionType`。## 通過複合種類建立綜合物件複合種類是包括有其他種類的種類。`Map`,`Struct`,`Array` 等,都是複合種類。下面是複合種類的列表:```goArrayChanFuncInterfaceMapPtrSliceStruct```可以像原始對象一樣使用零值來建立綜合物件。但是,僅使用一個空值而不做其他額外的操作的話,它們並不能被初始化。下面一節將詳細討論如何初始化複合種類。## 通過 type signature 來建立複合數組對象一個空的數組對象可以通過零值來建立。一個數組的零值是一個空的數組對象。下面是通過數組的 type signature 來建立一個數組的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有一個 `ArrayOf(int, Type)` 函數,可以從來建立包含類型為 `Type` 的特定長度的數組,下面是一個樣本:```gofunc CreateArray(t reflect.Type, length int) reflect.Value {var arrayType reflect.TypearrayType = reflect.ArrayOf(length, t)return reflect.Zero(arrayType)}```數組中的元素類型取決於傳遞進來的參數 `t`。數組的長度決定於參數 `length`。要想使用 reflect 包將值提取進一個數組中,最好的辦法是將數組作為介面處理。```gofunc extractArray(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Array {return nil, errors.New("invalid input")}var array interface{}array = v.Interface()return array, nil}```注意 `Slice()` 方法也可以用來提取數組的值,但是該方法需要在計算 reflect.Value 之前先將數群組轉換為一個可定址的數組。為了讓你的代碼更簡潔和可讀,最好還是是使用 `Interface()` 方法。## 通過 type signature 來建立複合通道對象一個空的通道對象可以通過零值來建立。一個通道的零值是一個空的通道對象。下面是通過通道的 type signature 來建立一個通道的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有兩個方法來建立一個通道。`ChanOf` 是用來建立通道的 type signature,`MakeChan(Type, int)` 方法可以用來給通道分配記憶體。下面是一個例子:```gofunc CreateChan(t reflect.Type, buffer int) reflect.Value {chanType := reflect.ChanOf(reflect.BothDir, t)return reflect.MakeChan(chanType, buffer)}```通道中元素的類型取決於傳遞進來的參數 `t`。通道的容量取決於參數 `buffer`。要想使用 reflect 包將值提取進一個通道中,最好的辦法是將通道作為介面處理。通道的方向通過傳入 `ChanOf` 的第一個參數來控制,可能的取值有:```goSendDirRecvDirBothDir````BothDir` 表明通道既可讀又可寫。`SendDir` 表明通道只能寫。`RecvDir` 表明通道只能讀。```gofunc extractChan(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Chan {return nil, errors.New("invalid input")}var ch interface{}ch = v.Interface()return ch, nil}```## 通過 type signature 來建立複合函數對象函數對象不能使用零值來建立。reflect 包有兩個方法來建立函數。`FuncOf` 方法是用於建立函數的 type signature,`MakeFunc(Type, func(args []Value) (results []Value)) Value` 方法可以用來給函數分配記憶體。下面是一個例子:```gofunc CreateFunc(fType reflect.Type,f func(args []reflect.Value) (results []reflect.Value))(reflect.Value, error) { if fType.Kind() != reflect.Func {return reflect.Value{}, errors.New("invalid input") }var ins, outs *[]reflect.Typeins = new([]reflect.Type)outs = new([]reflect.Type)for i := 0; i < fType.NumIn(); i++ {*ins = append(*ins, fType.In(i))}for i := 0; i < fType.NumOut(); i++ {*outs = append(*outs, fType.Out(i))}var variadic boolvariadic = fType.IsVariadic()return AllocateStackFrame(*ins, *outs, variadic, f), nil}func AllocateStackFrame(ins []reflect.Type,outs []reflect.Type,variadic bool,f func(args []reflect.Value) (results []reflect.Value)) reflect.Value { var funcType reflect.Type funcType = reflect.FuncOf(ins, outs, variadic) return reflect.MakeFunc(funcType, f)}````CreateFunc` 有兩個參數。第一個參數是你想建立的函數的 `type`,第二個是一個具體的函數,該函數實現了第一個參數的類型,但是其輸入和輸出都轉換為了 `reflect.Value` 對象。我已經建立好了這個函數,所以關於如何動態建立函數你就不用再重新造輪子了。為了使用它來建立函數,函數的 type signature 需要事先定義。比如:```gotype fn func(int) (int)```描述函數 signature 的 Type 結構體可以通過下面方法來建立:```govar funcVar fnvar funcType reflect.TypefuncType = reflect.TypeOf(funcVar)````funcType` 可以作為 `CreateFunc` 的第一個參數傳入。滿足 `funcType` 的 type signature 的函數可以作為第二個參數傳入。比如:```gofunc doubler (input int) (int) {return input * 2}```函數 `doubler` 將輸入值乘以 2,它接收一個 `int` 類型的參數並返回一個 `int` 類型的值。該函數滿足 `fn` 類型,但是不滿足通用的類型:```gof func (args []reflect.Value) (results []reflect.Value)```為了滿足通用的類型,需要一個等價版的 `doubler`,如下所示:```gofunc doublerReflect(args []reflect.Value) (result []reflect.Value) {if len(args) != 1 {panic(fmt.Sprintf("expected 1 arg, found %d", len(args)))}if args[0].Kind() != reflect.Int {panic(fmt.Sprintf("expected 1 arg of kind int, found 1 args of kind", args[0].Kind()))}var intVal int64intVal = args[0].Int()var doubleIntVal intdoubleIntVal = doubler(int(intVal))var returnValue reflect.ValuereturnValue = reflect.ValueOf(doubleIntVal)return []reflect.Value{returnValue}}````doublerReflect` 在功能上相當於 `doubler`,它滿足通用函數的 type signature。也就是說,它需要 1 個 `reflect.Value` 切片作為參數。並返回 1 個 `reflect.Value` 切片的值。輸入表示函數的輸入,傳回值表示正在產生的新函數的傳回值。調用 `CreateFunc` 可以用 `funcType` 和 `doublerReflect` 作為參數。下面的樣本顯示了對新建立的函數的調用。```gofunc main() {var funcVar fnvar funcType reflect.TypefuncType = reflect.TypeOf(funcVar)v, err := CreateFunc(funcType, doublerReflect)if err != nil {fmt.Printf("Error creating function %v\n", err)}input := 42ins := []reflect.Value([]reflect.Value{reflect.ValueOf(input)})outs := v.Call(ins)for i := range outs {fmt.Printf("%+v\n", outs[i].Interface())}}// Output: 84```## 通過 type signature 來建立複合映射對象一個空的映射對象可以通過零值來建立。一個映射的零值是一個空的映射對象。下面是通過映射的 type signature 來建立一個映射的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有一個 `MapOf(Type, Type)` 方法,該方法可以被用於建立映射,映射鍵的類型是傳入的第一個 `type` ,值的類型為第二個 `type`。下面是一個樣本:```gofunc CreateMap(key , elem reflect.Type) reflect.Value {var mapType reflect.TypemapType = reflect.MapOf(key, elem)return reflect.MakeMap(mapType)}```要想使用 reflect 包將值提取進一個映射對象中,最好的辦法是將映射作為介面處理。```gofunc extractMap(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Map {return nil, errors.New("invalid input")}var mapVal interface{}mapVal = v.Interface()return mapVal, nil}```注意到映射同樣可以使用 `MakeMapWithSize` 來分配空間。使用該方法的步驟同上面一樣,只是 `MakeMap` 可以用 `MakeMapWithSize` 來代替並且還需要傳入一個大小的參數。## 通過 type signature 來建立複合指標對象一個空的指標對象可以通過零值來建立。一個指標的零值是一個 `nil` 的指標對象。下面是通過指標的 type signature 來建立一個指標的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有一個 `PtrTo(Type)` 方法,可以用於建立指向 `Type` 類型的指標。比如:```gofunc CreatePtr(t reflect.Type) reflect.Value {var ptrType reflect.TypeptrType = reflect.PtrTo(t)return reflect.Zero(ptrType)}```注意到上面的功能同樣可以使用 `reflect.New(t)` 來實現。指標所指向的元素的類型決定於傳入參數 `t`。要想使用 reflect 包將值提取進一個對象中,最好的辦法是首先使用 `Indirect()` 或者 `Elem()` 方法來得到指標所指向對象的值,然後將該值作為介面處理。```gofunc extractElement(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Ptr {return nil, errors.New("invalid input")}var elem reflect.Valueelem = v.Elem()var elem interface{}elem = v.Interface()return elem, nil}```## 通過 type signature 來建立複合切片對象一個空的切片對象可以通過零值來建立。一個切片的零值是一個空的切片對象。下面是通過切片的 type signature 來建立一個切片對象的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value {return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有一個 `SliceOf(Type)` 的方法可以用於建立包含 `Type` 類型的切片。下面是使用方法:```gofunc CreateSlice(t reflect.Type) reflect.Value {var sliceType reflect.TypesliceType = reflect.SliceOf(length, t)return reflect.Zero(sliceType)}```切片中元素的類型由傳入的參數 `t` 決定。要想使用 reflect 包將值提取進一個切片對象中,最好的辦法是將該切片作為介面處理。```gofunc extractSlice(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Slice {return nil, errors.New("invalid input")}var slice interface{}slice = v.Interface()return slice, nil}```## 通過 type signature 來建立複合結構體對象一個空的結構體對象可以通過零值來建立。一個結構體的零值是一個空的結構體對象。下面是通過結構體的 type signature 來建立一個結構體對象的例子:```gofunc CreateCompositeObjects(t reflect.Type) reflect.Value { return reflect.Zero(t)}```該函數會建立一個包含一個任意空綜合物件的 `reflect.Value` 類型的結構體。reflect 包有一個 `StructOf([]reflect.StructFields)` 的方法可以用於建立包含 `StructField` 中定義的欄位類型的結構體。下面是使用方法:```gofunc CreateStruct(fields []reflect.StructField) reflect.Value {var structType reflect.TypestructType = reflect.StructOf(fields)return reflect.Zero(structType)}```要想使用 reflect 包將值提取進一個結構體對象中,最好的辦法是將該結構體作為介面處理。```gofunc extractStruct(v reflect.Value) (interface{}, error) {if v.Kind() != reflect.Struct {return nil, errors.New("invalid input")}var st interface{}st = v.Interface()return st, nil}```## 結論這是一個關於使用 reflect 包來動態建立任意 Go 類型對象的完整教程。我提供了建立 `Func` 類型的便利方法,因為它比其他類型更複雜,如果不仔細設計,很容易汙染您的程式碼程式庫。請繼續關注我的下一篇博文,將任何類型轉換為 Golang 中的其他類型!接下來,我將解釋如何在 Golang 中編寫 JIT (即時),然後介紹使用 reflect 來產生代碼。

via: https://medium.com/kokster/go-reflection-creating-objects-from-types-part-ii-composite-types-69a0e8134f20

作者:Sidhartha Mani 譯者:ParadeTo 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

589 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.