上一篇文章 說到了 Golang 中的反射的一些基本規則,重點就是文章中最后的三點,但是這篇文章並沒有說如何在反射中調用函數和方法,這就是接下來要說的。
反射中調用 函數
眾所周知,Golang 中的函數是可以像普通的 int、float 等類型變量那樣作為值的,例如:
package main import "fmt" func hello() { fmt.Println("Hello world!") } func main() { hl := hello hl() }
prints:
hello world!
既然函數可以像普通的類型變量一樣可以的話,那么在反射機制中就和不同的變量是一樣的,在反射中 函數 和 方法 的類型(Type)都是 reflect.Func,如果要調用函數的話,可以通過 Value 的 Call() 方法,例如:
func main() { hl := hello fv := reflect.ValueOf(hl) fmt.Println("fv is reflect.Func ?", fv.Kind() == reflect.Func) fv.Call(nil) }
prints:
fv is reflect.Func? true hello world!
Value 的 Call() 方法的參數是一個 Value 的 slice,對應的反射函數類型的參數,返回值也是一個 Value 的 slice,同樣對應反射函數類型的返回值。通過這個例子,相信你一看就明白了:
func prints(i int) string { fmt.Println("i =", i) return strconv.Itoa(i) } func main() { fv := reflect.ValueOf(prints) params := make([]reflect.Value, 1) // 參數 params[0] = reflect.ValueOf(20) // 參數設置為20 rs := fv.Call(params) // rs作為結果接受函數的返回值 fmt.Println("result:", rs[0].Interface().(string)) // 當然也可以直接是 rs[0].Interface() }
prints:
i = 20 result: 20
反射中調用 方法
上面說了在反射中調用函數的例子,接下來我們要談談反射中方法的調用。函數和方法可以說其實本質上是相同的,只不過方法與一個“對象”進行了“綁定”,方法是“對象”的一種行為,這種行為是對於這個“對象”的一系列操作,例如修改“對象”的某個屬性,如下所示:
type MyType struct { i int name string } func (mt *MyType) SetI(i int) { mt.i = i } func (mt *MyType) SetName(name string) { mt.name = name } func (mt *MyType) String() string { return fmt.Sprintf("%p", mt) + "--name:" + mt.name + " i:" + strconv.Itoa(mt.i) }
好了,現在類型和其對應的方法都已經准備好了,那接下來就是如何使用的問題了,我們有了上面調用函數的經驗,只需要再了解一點知識就可以使用了,這一點知識就是 Method 和 MethodByName 的API,好了,現在都准備好了,我們就看看如何使用吧。
func main() { myType := &MyType{22, "golang"} //fmt.Println(myType) // 就是檢查一下myType對象內容 //println("---------------") mtV := reflect.ValueOf(&myType).Elem() // 也可以使用 //mtV := reflect.ValueOf(myType) fmt.Println("Before:", mtV.MethodByName("String").Call(nil)[0]) params := make([]reflect.Value, 1) params[0] = reflect.ValueOf(18) mtV.MethodByName("SetI").Call(params) params[0] = reflect.ValueOf("reflection test") mtV.MethodByName("SetName").Call(params) fmt.Println("After:", mtV.MethodByName("String").Call(nil)[0]) }
prints:
Before: address:0x18245050--name:golang i i:22 After: address:0x18245050--name:reflection test i:18
需要注意的是上面打印的地址是對象在內存的地址,如果你也運行了這段代碼,結果這個地址應該是不同的。
咦,就這樣結束了嗎?當然不是,細心的讀者會發現上面提到的 Method 好像沒用到啊,恩,是的,聰明的你一看 API 的介紹我相信你就知道如何將上面的代碼轉換成用 Method() 方法達到同樣的效果:
mtV := reflect.ValueOf(&myType).Elem() fmt.Println("Before:",mtV.Method(2).Call(nil)[0]) params = make([]reflect.Value,1) params[0] = reflect.ValueOf(18) mtV.Method(0).Call(params) params[0] = reflect.ValueOf("reflection test") mtV.Method(1).Call(params) fmt.Println("After:",mtV.Method(2).Call(nil)[0])
參考: