Golang反射技術初始入門

張君鴻發表於2019-03-24

反射是Go語言學習中一個比較難的點,需要好好探索一下。

什麼反射

我們知道,無論是int,float,bool等基礎資料型別,亦或是array,slice,map,chan等引用型別,當使用這些型別來定義的變數,在程式編譯時,編譯器已經知道變數的具體型別和具體值。

但很多時候,當我們使用介面型別(interface{})定義變數時,介面型別的具體型別與具體值,需要在程式執行時才能確定,且可以動態變化,因此需要一種技術來檢測變數在程式執行中的具體型別和值。

Go反射技術就是這樣一種用來檢查未知型別和值的機制與方法。

為什麼需要反射

當我們需要實現一個通用的函式時,比如實現一個類似fmt.Sprint這樣的列印函式時,可以根據不同的資料型別,返回出不同格式的資料,我們也實現一個類似fmt.Sprint()函式但只可以列印一個引數的Sprint函式,程式碼如下:

type stringer interface {
    String() string
}
func Sprint(x interface{}) string {
    switch x := x.(type) {
    case stringer:
        return x.String()
    case string:
        return x
    case int:
        return strconv.Itoa(x)
        // 還有int16, uint32,或者更多我們自定義的未知型別.
    case bool:
        if x {
            return "true"
        }
        return "false"
    default:
        // 預設返回值
        return "???"
    }
}
複製程式碼

在上面列印函式中,我們只是判斷了幾種基礎型別,但這是不夠,還有許多型別沒有判斷,雖然我們可以在上面的switch結構中繼續增加分支判斷,但在實際的程式中,還更多自定義的未知型別,因此需要使用反射技術來實現。

reflect.Type和reflect.Value

Go語言反射技術是由reflect包來實現,這個包主要定義了reflect.Type和reflect.Value兩個重要的型別。

reflect.Type

reflect.Type是一個介面,代表一個的具體型別,使用reflect.TypeOf()函式,可以返回reflect.Type的實現,reflect.TypeOf()方法可以接收任何型別的引數,如下:

t := reflect.TypeOf(100)
fmt.Println(t)//int
複製程式碼

reflect.Value

reflect.Value是一個reflect包中定義的結構體,代表一個型別的具體值,使用reflect.ValueOf()函式可以返回一個reflect.Value值,reflect.ValueOf()可以接收任意型別的引數。

使用reflect.Value中的Type()方法,可以返回對應的reflect.Type。

v := reflect.ValueOf(10)
fmt.Println(v)
t := v.Type()
fmt.Println(t)
複製程式碼

因此,我們可以使用反射來修改上面的Spring函式。

func Sprint(vv interface{}) string {
    v := reflect.ValueOf(vv)
    switch v.Kind() {
    case reflect.Invalid:
        return "invalid"
    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16,
        reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return strconv.FormatUint(v.Uint(), 10)
    case reflect.Bool:
        return strconv.FormatBool(v.Bool())
    case reflect.String:
        return strconv.Quote(v.String())
    case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
        return v.Type().String() + " 0x" +
        strconv.FormatUint(uint64(v.Pointer()), 16)
    default: 
        return v.Type().String() + " value"
    }
}
複製程式碼

總結

Golang提供的反射reflect包,是用於檢測型別、修改型別值、呼叫型別方法和其他操作的強大技術,在其他的庫或框架中都有使用,但還是應該慎用。

除了我們經常使用的fmt包是應用反射實現的之外,encoding/json、encoding/xml等包也是如此。

其實在一些Web框架中,將http請求引數繫結到模型中,在ORM框架中,將資料表查詢結果繫結到模型中,應用的都是反射技術。

相關文章