反射是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框架中,將資料表查詢結果繫結到模型中,應用的都是反射技術。