GoLang 反射詳解
What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.
你所浪費的今天是昨天死去的人奢望的明天; 你所厭惡的現在是未來的你回不去的曾經。
Go是靜態型別語言。每個變數都擁有一個靜態型別,這意味著每個變數的型別在編譯時都是確定的:int,float32, *AutoType, []byte, chan []int 諸如此類。
動靜型別
編譯時就知道變數型別的是靜態型別;執行時才知道一個變數型別的叫做動態型別。
1. 靜態型別
靜態型別就是變數宣告時的賦予的型別。比如:
type MyInt int // int 就是靜態型別
type A struct{
Name string // string就是靜態
}
var i *int // *int就是靜態型別
2. 動態型別
動態型別:執行時給這個變數複製時,這個值的型別(如果值為nil的時候沒有動態型別)。一個變數的動態型別在執行時可能改變,這主要依賴於它的賦值(前提是這個變數時介面型別)。
var A interface{} // 靜態型別interface{} A = 10 // 靜態型別為interface{} 動態為int A = "String" // 靜態型別為interface{} 動態為string var M *int A = M // 猜猜這個呢?
來看看這個例子:
//定義一個介面 type Abc interface{ String() string } // 型別 type Efg struct{ data string } // 型別Efg實現Abc介面 func (e *Efg)String()string{ return e.data } // 獲取一個*Efg例項 func GetEfg() *Efg{ return nil } // 比較 func CheckAE(a Abc) bool{ return a == nil } func main() { efg := GetEfg() b := CheckAE(efg) fmt.Println(b) os.Exit(1) }
關於動靜態型別就到這裡,詳細請自行Google,百度吧。
反射
那麼什麼時候下使用反射呢?
有時候你想在執行時使用變數來處理變數,這些變數使用編寫程式時不存在的資訊。也許你正試圖將來自檔案或網路請求的資料對映到變數中。也許建立一個適用於不同型別的tool。在這些情況下,你需要使用反射。反射使您能夠在執行時檢查型別。它還允許您在執行時檢查,修改和建立變數,函式和結構。
型別
你可以使用反射來獲取變數的型別: var t := reflect.Typeof(v)。返回值是一個reflect.Type型別。該值有很多定義好的方法可以使用。
Name()
返回型別的名稱。 但是像切片或指標是沒有型別名稱的,只能返回空字串。
Kind()
Kind有slice, map , pointer指標,struct, interface, string , Array, Function, int或其他基本型別組成。Kind和Type之前要做好區分。如果你定義一個 type Foo struct {}, 那麼Kind就是struct, Type就是Foo。
*小知識點:反射變數對應的Kind方法的返回值是基型別,並不是靜態型別。下面的例子中:
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
變數v的Kind依舊是reflect.Int,而不是MyInt這個靜態型別。Type可以表示靜態型別,而Kind不可以。
*注意點: 在使用refelct包時, reflect包會假定你已經知道所做的是什麼,否則引發panic。 例如你呼叫了與當前reflect.Type 不同的型別上的方法,那麼就會引發panic。
Elem()
如果你的變數是一個指標、map、slice、channel、Array。那麼你可以使用reflect.Typeof(v).Elem()來確定包含的型別。
案例程式碼
type Foo struct {
A int `tag1:"Tag1" tag2:"Second Tag"`
B string
}
func main(){
// Struct
f := Foo{A: 10, B: "Salutations"}
// Struct型別的指標
fPtr := &f
// Map
m := map[string]int{"A": 1 , "B":2}
// channel
ch := make(chan int)
// slice
sl:= []int{1,32,34}
//string
str := "string var"
// string 指標
strPtr := &str
tMap := examiner(reflect.TypeOf(f), 0)
tMapPtr := examiner(reflect.TypeOf(fPtr), 0)
tMapM := examiner(reflect.TypeOf(m), 0)
tMapCh := examiner(reflect.TypeOf(ch), 0)
tMapSl := examiner(reflect.TypeOf(sl), 0)
tMapStr := examiner(reflect.TypeOf(str), 0)
tMapStrPtr := examiner(reflect.TypeOf(strPtr), 0)
fmt.Println("tMap :", tMap)
fmt.Println("tMapPtr: ",tMapPtr)
fmt.Println("tMapM: ",tMapM)
fmt.Println("tMapCh: ",tMapCh)
fmt.Println("tMapSl: ",tMapSl)
fmt.Println("tMapStr: ",tMapStr)
fmt.Println("tMapStrPtr: ",tMapStrPtr)
}
// 型別以及元素的型別判斷
func examiner(t reflect.Type, depth int) map[int]map[string]string{
outType := make(map[int]map[string]string)
// 如果是一下型別,重新驗證
switch t.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
fmt.Println("這幾種型別Name是空字串:",t.Name(), ", Kind是:", t.Kind())
// 遞迴查詢元素型別
tMap := examiner(t.Elem(), depth)
for k, v := range tMap{
outType[k] = v
}
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
f := t.Field(i) // reflect欄位
outType[i] = map[string]string{
"Name":f.Name,
"Kind":f.Type.String(),
}
}
default:
// 直接驗證型別
outType = map[int] map[string]string{depth:{"Name":t.Name(), "Kind":t.Kind().String()}}
}
return outType
}
執行結果:
其中t.Field(index) 必須使用在Struct上 , 所以,細讀文件才行
利用反射讀取,設定,建立
看完了上面關於reflect檢測變數型別外,我們使用反射讀取、設定和建立。
要想讀取一個變數的值,首先需要一個reflect.Valueof( var ) 例項(reflectVal := reflect.Valueof(var)), 同時也可以獲取變數的型別了。
要想修改一個變數的值,那麼必須通過該變數的指標地址 , 取消指標的引用 。通過refPtrVal := reflect.Valueof( &var )的方式獲取指標型別,你使用refPtrVal.elem( ).set(一個新的reflect.Value)來進行更改,傳遞給set()的值也必須是一個reflect.value。
要想建立一個值,那麼使用NewPtrVal := reflect.New( vartype ) 傳遞一個reflect.Type型別。 返回的指標型別就可以使用以上修改的方式寫入值。
最後,你可以通過呼叫interface()方法返回一個正常的變數。因為Golang沒有泛型,變數的原始型別丟失;該方法返回一個型別為interface{} 的值。如果建立了一個指標以便可以修改該值,則需要使用elem().interface()來反引用reflect的指標。在這兩種情況下,您都需要將空介面轉換為實際型別才能使用它。
例項程式碼:
type Foo struct {
A int `tag1:"Tag1" tag2:"Second Tag"`
B string
}
func main(){
// 反射的使用
s := "String字串"
fo := Foo{A: 10, B: "欄位String字串"}
sVal := reflect.ValueOf(s)
// 在沒有獲取指標的前提下,我們只能讀取變數的值。
fmt.Println(sVal.Interface())
sPtr := reflect.ValueOf(&s)
sPtr.Elem().Set(reflect.ValueOf("修改值1"))
sPtr.Elem().SetString("修改值2")
// 修改指標指向的值,原變數改變
fmt.Println(s)
fmt.Println(sPtr) // 要注意這是一個指標變數,其值是一個指標地址
foType := reflect.TypeOf(fo)
foVal := reflect.New(foType)
// foVal.Elem().Field(0).SetString("A") // 引發panic
foVal.Elem().Field(0).SetInt(1)
foVal.Elem().Field(1).SetString("B")
f2 := foVal.Elem().Interface().(Foo)
fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B)
}
執行結果:
記憶10秒。
建立slice, map, chan
除了建立內建和使用者定義型別的例項之外,還可以使用反射來建立通常需要make功能的例項。使用reflect.Makeslice,reflect.Makemap和reflect.Makechan函式來製作slice,map或channel。
// 反射建立map slice channel
intSlice := make([]int, 0)
mapStringInt := make(map[string]int)
sliceType := reflect.TypeOf(intSlice)
mapType := reflect.TypeOf(mapStringInt)
// 建立新值
intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
mapReflect := reflect.MakeMap(mapType)
// 使用新建立的變數
v := 10
rv := reflect.ValueOf(v)
intSliceReflect = reflect.Append(intSliceReflect, rv)
intSlice2 := intSliceReflect.Interface().([]int)
fmt.Println(intSlice2)
k := "hello"
rk := reflect.ValueOf(k)
mapReflect.SetMapIndex(rk, rv)
mapStringInt2 := mapReflect.Interface().(map[string]int)
fmt.Println(mapStringInt2)
執行結果:
建立函式
使用reflect.Makefunc()建立,這個函式需要我們想要做的函式的reflect.type和一個輸入引數是[] reflect.value型別的slice,其輸出引數也是型別[] reflect.value的閉包。下面是一個簡單的例子,檢測任意給定函式的執行時長:
package main
import (
"reflect"
"time"
"fmt"
"runtime"
)
/*
將建立Func封裝, 非reflect.Func型別會panic
當然makeFunc的閉包函式表示式型別是固定的,可以查閱一下文件。
細讀文件的reflect.Value.Call()方法。
*/
func MakeTimedFunction(f interface{}) interface{} {
rf := reflect.TypeOf(f)
if rf.Kind() != reflect.Func {
panic("非Reflect.Func")
}
vf := reflect.ValueOf(f)
wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
start := time.Now()
out := vf.Call(in)
end := time.Now()
fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
return out
})
return wrapperF.Interface()
}
func time1() {
fmt.Println("time1Func===starting")
time.Sleep(1 * time.Second)
fmt.Println("time1Func===ending")
}
func time2(a int) int {
fmt.Println("time2Func===starting")
time.Sleep(time.Duration(a) * time.Second)
result := a * 2
fmt.Println("time2Func===ending")
return result
}
func main() {
timed := MakeTimedFunction(time1).(func())
timed()
timedToo := MakeTimedFunction(time2).(func(int) int)
time2Val := timedToo(5)
fmt.Println(time2Val)
}
執行結果:
分析:
reflect.Value.Call(var) 文件如下:
擴充套件Call()
首先我們可以確認一點就是,函式像普通變數一樣, 假如Foo()是一個函式, 那麼,f := Foo 也是成立的。
在反射中 函式 和 方法 的型別(Type)都是 reflect.Func,如果要呼叫函式的話,可以通過 Value 的 Call() 方法,例如:
func Halou(){
fmt.Println("This is Halou函式! 6666")
}
func main(){
// Call()擴充套件
h := Halou
hVal := reflect.ValueOf(h)
fmt.Println("hVal is reflect.Func ?", hVal.Kind() == reflect.Func)
hVal.Call(nil)
}
執行結果:
reflect.Value 的 Call() 方法的引數是一個 reflect.Value 的 slice,對應所反射函式型別的引數,返回值也是一個 reflect.Value 的 slice,同樣對應所反射函式型別的返回值。所以:
func Halou2(s string)string{
return "接受到引數:"+s+", 但這是返回值!"
}
func main(){
h2 := reflect.ValueOf(Halou2)
params := []reflect.Value{
reflect.ValueOf("引數1"),
}
h2ReturnVal := h2.Call(params)
fmt.Println(h2ReturnVal)
}
留下個問題吧!
函式本事獨立與任何個體之外存活的,方法卻要依託物件的存在。方法是“物件”的一種行為。那麼如何通過反射呼叫方法呢?