在計算機科學中,反射是指計算機程序在運行時(Run time)能夠訪問、檢測和修改它自己狀態或行爲的一種能力。用比喻來講,反射就是程序在運行的時候可以「觀察」而且修改本身的行爲。bash
Go 語言提供了一種機制在運行時更新變量和檢查它們的值、調用它們的方法,可是在編譯時並不知道這些變量的具體類型,這稱爲反射機制(refletion)。函數
Go 語言官方自帶的 reflect 包就是實現反射相關的,reflect 包定義了各類類型,實現了反射的各類函數,經過它們能夠在運行時檢測類型的信息、改變類型的值。工具
須要反射的 2 個常見場景:佈局
Go 語言的 fmt.Printf 函數中的格式化邏輯就是使用反射處理相似以上存在的問題來實現的。測試
下面嘗試實現一個相似 fmt.Printf 功能的函數,爲了簡單起見,函數只接收一個參數,而後返回和 fmt.Sprint 相似的格式化後的字符串,函數名也叫 Sprint。ui
首先用 switch 類型分支來測試輸入參數是否實現了 String 方法,若是是就調用該方法。而後繼續增長類型測試分支,檢查這個值的動態類型是不是 string、int、bool 等基礎類型,並在每種狀況下執行相應的格式化操做。url
func Sprint(x interface{}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
// ...similar cases for int16, uint32, and so on...
case bool:
if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
複製代碼
以上函數雖然實現了部分類型的格式化輸出,可是如何處理其它相似 []float6四、map[string][]string 等類型呢?固然能夠添加更多的測試分支,可是這些組合類型的數目基本是無窮的。還有如何處理相似url.Values這樣的具名類型呢?即便類型分支能夠識別出底層的基礎類型是 map[string][]string,可是它並不匹配 url.Values 類型,由於它們是兩種不一樣的類型,並且 switch 類型分支也不可能包含每一個相似 url.Values 的類型,這會致使對這些庫的依賴。spa
沒有辦法來檢查未知類型的表示方式,被卡住了。這就是爲什麼須要反射的緣由。設計
interface 是 Go 語言實現抽象的一個很是強大的工具。當向接口變量賦予一個實體類型的時候,接口會存儲實體的類型信息,反射就是經過接口的類型信息實現的,反射創建在類型的基礎上。指針
Go 語言關於類型設計的一些原則:
變量包括 type, value 這兩部分。其中 type 包括 static type 和 concrete type,static type 是在編寫程序時看見的類型(即變量聲明時賦予的類型,如int、string),concrete type 是 runtime 系統時看見的類型(即運行時給這個變量賦值後,該變量的類型)。
類型斷言可否成功,取決於變量的 concrete type,而不是 static type。因此,一個 reader 變量若是它的 concrete type 也實現了 write 方法,它能夠被類型斷言爲 writer。
反射創建在類型之上,Go 語言聲明變量時指定的 type 是 static type,在建立變量的時候就已經肯定。反射主要與 Go 語言的 interface 類型相關(它的 type 是 concrete type ),只有 interface 類型纔有反射一說。
Go 語言中,每一個 interface 變量都有一個對應 pair,pair 中記錄了實際變量的值和類型:
(value, type)
複製代碼
以上,value 是實際變量值,type 是實際變量的類型。一個 interface{} 類型的變量包含了2個指針,一個指針指向值的類型【對應 concrete type】,另一個指針指向實際的值【對應 value】。
Go 語言的反射功能由 reflect 包提供,它實現了運行時反射,使用它能識別 interface{} 變量的底層具體類型和具體值。
1. reflect.Type 和 reflect.Value
reflect 包定義了兩個重要的類型:Type 和 Value。reflect.Type 表示 interface{} 的具體類型,而 reflect.Value 表示它的具體值。reflect.TypeOf() 和 reflect.ValueOf() 兩個函數能夠分別返回 reflect.Type 和 reflect.Value。
- TypeOf 用來動態獲取輸入參數接口中的值的類型,若是接口爲空則返回nil
- ValueOf用來獲取輸入參數接口中的數據的值,若是接口爲空則返回0
即,reflect.TypeOf() 是獲取 pair 中的 type,reflect.ValueOf() 獲取 pair 中的value,示例以下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("type: ", reflect.TypeOf(num))
fmt.Println("value: ", reflect.ValueOf(num))
}
複製代碼
運行結果:
type: float64
value: 1.2345
複製代碼
2. relfect.Kind
reflect 包中還有一個重要的類型:Kind。 在反射包中,Kind 和 Type 的類型可能看起來很類似,但在下面程序中,能夠很清楚地看出它們的不一樣之處。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
複製代碼
輸出:
Type main.order
Kind struct
複製代碼
由上可知:Type 表示 interface{} 的實際類型(在這裏是 main.Order),而 Kind 表示該類型的特定類別(在這裏是 struct)。
當執行 reflect.ValueOf(interface) 以後,就獲得了一個類型爲 「relfect.Value」 變量,能夠經過它自己的 Interface() 方法得到接口變量的真實內容,而後能夠經過類型判斷進行轉換,轉換爲原有真實類型。
realValue := value.Interface().(已知的類型)
複製代碼
示例以下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 能夠理解爲「強制轉換」,可是須要注意的時候,轉換的時候,若是轉換的類型不徹底符合,則直接panic
// Golang 對類型要求很是嚴格,類型必定要徹底符合
// 以下兩個,一個是*float64,一個是float64,若是弄混,則會panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
複製代碼
運行結果:
0xc42000e238
1.2345
複製代碼
說明:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func main() {
user := User{1, "Allen.Wu", 25}
DoFiledAndMethod(user)
}
// 經過接口來獲取任意參數,而後一一揭曉
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input)
fmt.Println("get Type is :", getType.Name())
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue)
// 獲取方法字段
// 1. 先獲取interface的reflect.Type,而後經過NumField進行遍歷
// 2. 再經過reflect.Type的Field獲取其Field
// 3. 最後經過Field的Interface()獲得對應的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 獲取方法
// 1. 先獲取interface的reflect.Type,而後經過.NumMethod進行遍歷
for i := 0; i < getType.NumMethod(); i++ {
m := getType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
複製代碼
運行結果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)
複製代碼
說明
經過運行結果能夠得知獲取未知類型的 interface 的具體變量及其類型的步驟爲:
經過運行結果能夠得知獲取未知類型的interface的所屬方法(函數)的步驟爲:
reflect.Value 是經過 reflect.ValueOf(x) 得到的,只有當 x 是指針的時候,才能夠經過 reflec.Value 修改實際變量 x 的值,即:要修改反射類型的對象就必定要保證其值是 「addressable」 的。 示例以下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("old value of pointer:", num)
// 經過reflect.ValueOf獲取num中的reflect.Value,注意,參數必須是指針才能修改其值
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()
fmt.Println("type of pointer:", newValue.Type())
fmt.Println("settability of pointer:", newValue.CanSet())
// 從新賦值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num)
// 若是reflect.ValueOf的參數不是指針,會如何?
pointer = reflect.ValueOf(num)
//newValue = pointer.Elem() // 若是非指針,這裏直接panic,「panic: reflect: call of reflect.Value.Elem on float64 Value」
}
複製代碼
運行結果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77
複製代碼
說明
示例以下:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
}
func (u User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
// 如何經過反射來進行方法的調用?
// 原本能夠用u.ReflectCallFuncXXX直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是MethodByName,而後經過反射調動mv.Call
func main() {
user := User{1, "Allen.Wu", 25}
// 1. 要經過反射來調用起對應的方法,必需要先經過reflect.ValueOf(interface)來獲取到reflect.Value,獲得「反射類型對象」後才能作下一步處理
getValue := reflect.ValueOf(user)
// 必定要指定參數爲正確的方法名
// 2. 先看看帶有參數的調用方法
methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
methodValue.Call(args)
// 必定要指定參數爲正確的方法名
// 3. 再看看無參數的調用方法
methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
args = make([]reflect.Value, 0)
methodValue.Call(args)
}
複製代碼
運行結果:
ReflectCallFuncHasArgs name: wudebao, age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs
複製代碼
說明
要經過反射來調用起對應的方法,必需要先經過 reflect.ValueOf(interface) 來獲取到 reflect.Value,獲得「反射類型對象」後才能作下一步處理
reflect.Value.MethodByName 這 .MethodByName,須要指定準確真實的方法名字,若是錯誤將直接 panic,MethodByName 返回一個函數值對應的 reflect.Value 方法的名字。
[]reflect.Value,這個是最終須要調用的方法的參數,能夠沒有或者一個或者多個,根據實際參數來定。
reflect.Value 的 Call 這個方法,這個方法將最終調用真實的方法,參數務必保持一致,若是 reflect.Value'Kind 不是一個方法,那麼將直接 panic。
原本能夠用 u.ReflectCallFuncXXX 直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是 MethodByName,而後經過反射調用 methodValue.Call