Go提供了各種變數、切片、結構體等等特性,我們可以非常方便的定義與使用它們,例如,當你想定義一個結構體的型別,只需要簡單地定義:
type A struct {
Name string
}
然而,當需要處理處理動態資料結構時,我們無法在編譯階段就知道未知資料的結構,其中一個非常經典的使用情景就是對Json串的Marshal,此時,就該reflect包出場了,它提供了在運行時創建、更新某種型別以及獲取該型別的各種資訊的能力,有了它,我們不僅能有效處理動態資料型別,還可以大大提高代碼的復用性、可讀性,
Type
在reflect包中,是用Type來描述Go中某個物件的型別,并提供了一系列方法,來獲取型別的相關資訊,一般通過呼叫TypeOf來獲取一個任意變數的型別Type,
例如,Name()回傳的就是該型別的具體名稱,String()回傳型別的字串表示,
值得注意的是Kind()方法,它回傳的是該型別的類別,這似乎有點拗口,但其實十分好理解,舉個例子,type A struct{} ,它的型別是A而類別是struct,通常,在開始階段,我們會先判斷傳入的interface的類別,從而避免panic,因為有些方法只適用于某種類別,隨意使用的話代碼很容易panic,例如NumField()方法,只能用以獲取Kind為結構體的欄位數量,
還有一個方法Elem(),回傳Type的子元素的Type,舉個例子,若Type為指標,那么Elem()回傳指標所指向的Type,若為切片,則Elem()回傳切片元素的型別Type,例如*[]int,它的Elem()方法回傳[]int的Type,而[]int的Elem()方法回傳int的Type,
import (
"fmt"
"reflect"
)
type A []int
func printInfo(t reflect.Type) {
fmt.Printf("Kind = %s\tName = %s\n", t.Kind(), t.Name())
}
func main() {
a := &A{}
printInfo(reflect.TypeOf(a))
printInfo(reflect.TypeOf(a).Elem())
printInfo(reflect.TypeOf(a).Elem().Elem())
}
輸出如下:
Kind = ptr Name =
Kind = slice Name = A
Kind = int Name = int
Value
而Value描述了在Go運行時某個物件的值,我們可以針對它進行增刪改查之類的操作,一般通過ValueOf方法來獲取物件的Value,
通常情況下,我們可以通過Set()方法來修改變數的值,例如下述代碼
var a = 1
val := reflect.ValueOf(&a)
val.Elem().Set(reflect.ValueOf(2))
fmt.Printf("a = %d", a)
輸出:
a = 2
可以看到,變數a的值由1被修改為2了,
使用舉例
動態初始化結構體
實際作業中,struct通常用來表示某種資料結構(或物件),是十分簡潔易懂的,然而,缺點也很明顯,即其表達能力很有限,比如,你想指定某個欄位的默認值,你不得不在建構式中手動指定,這種方式雖然可行,但是不夠優雅,可讀性也很差,
type DS struct {
FieldOne string
}
func NewDS() *DS {
return &DS{
FieldOne: "something",
}
}
那么該如何優化呢?很簡單,即利用欄位的tag資訊,例如,下述代碼,我在tag中設定了默認值,
type DS struct {
FieldOne string `default:"something"`
}
然后,我使用一個初始化函式initStruct()來讀取tag并設定欄位默認值,
func NewDS() *DS {
ds := &DS{}
initStruct(ds)
fmt.Printf("FieldOne = %s", ds.FieldOne)
return ds
}
func initStruct(v interface{}) error {
e := reflect.Indirect(reflect.ValueOf(v))
if e.Kind() != reflect.Struct {
return errors.New("v must be struct")
}
et, ev := e.Type(), e
for i := 0; i < et.NumField(); i++ {
field, val := et.Field(i), ev.Field(i)
defaultValue, ok := field.Tag.Lookup("default")
if !ok {
continue
}
switch field.Type.Kind() {
case reflect.String:
val.SetString(defaultValue)
case reflect.Int:
if x, err := strconv.ParseInt(defaultValue, 10, 64); err != nil {
val.SetInt(x)
}
// 針對不同Kind,將defaultValue轉換為對應型別并賦值
...
}
}
return nil
}
至此,我們就可以既方便又優雅地給結構體設定默認值了,當然,你還可以在tag中設定其他動態屬性來動態更改結構體,
動態創建Map
通常情況下,我們是通過make來創建一個map,而有了reflect包后,我們也可以通過reflet包來動態地創建一個map,
這里,我們有個需求,需要將一個代表長方形的結構體轉換為一個map,并且存在額外要求,例如浮點欄位只保留兩位小數且轉換為字串,
首先,定義一個名為Rectangle的結構體來代表一個長方形
type Rectangle struct {
Name string
Unit string
Length float64
Width float64
}
然后,使用一個convert函式,將其轉換為map,
func convert(rectangle *Rectangle) (res map[string]string, err error) {
e := reflect.Indirect(reflect.ValueOf(rectangle))
if e.Kind() != reflect.Struct {
return nil, errors.New("v must be struct")
}
et, ev := e.Type(), e
var mapStringType = reflect.TypeOf(make(map[string]string))
mapReflect := reflect.MakeMap(mapStringType)
for i := 0; i < et.NumField(); i++ {
field, val := et.Field(i), ev.Field(i)
switch field.Type.Kind() {
case reflect.String:
mapReflect.SetMapIndex(reflect.ValueOf(field.Name), reflect.ValueOf(val.String()))
case reflect.Float64:
s := strconv.FormatFloat(val.Float(), 'f', 2, 64)
mapReflect.SetMapIndex(reflect.ValueOf(field.Name), reflect.ValueOf(s))
// other cases
...
}
}
return mapReflect.Interface().(map[string]string), nil
}
最后,我們可以列印出轉換后的map,
func main() {
res, _ := convert(&Rectangle{
Name: "rec-1",
Unit: "cm",
Length: 12.121764,
Width: 5.989681,
})
fmt.Printf("res = %+v", res)
}
輸出:
res = map[Length:12.12 Name:rec-1 Unit:cm Width:5.99]
總結
至此,對于reflect的簡單介紹已完畢,相信你已經有了一個大概的認知了,是不是覺得這個包很強大,想躍躍欲試呢?但是,在此之前,還是要提醒你要銘記以下注意點,
reflect大多只能適用于動態資料型別的場景,且較為危險,因此能使用原生型別盡量使用原生型別,- 書寫要小心,錯誤使用reflect很容易panic,你需要確保你的型別使用了正確的相關方法,并提前回傳錯誤,
- 編程界沒有銀彈,因此
reflect也不是萬能,例如你無法動態創建結構體的方法,
本人才疏學淺,文章難免有些不足之處,非常歡迎大家評論指出,
參考
-
Learning to Use Go Reflection - Capital One Tech - Medium
-
The Laws of Reflection - The Go Blog - Golang
-
Reflection in Golang - golangbot.com
-
Go 語言反射的實作原理| Go 語言設計與實作 - 面向信仰編程
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/13817.html
標籤:Go
上一篇:Go語言中INI組態檔格式決議
