1. 程式人生 > >GoLang 反射詳解

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)
}

留下個問題吧!

函式本事獨立與任何個體之外存活的,方法卻要依託物件的存在。方法是“物件”的一種行為。那麼如何通過反射呼叫方法呢?