Golang反射-上篇

1、反射的定義
It’s a great source of confusion ~ (引用自官方部落格)
反射是指在執行時動態的訪問和修改任意型別物件的結構和成員,在 go
語言中提供 reflect
包提供反射的功能,每一個變數都有兩個屬性:型別 Type
和值 Value
反射能夠自描述自控制
例如 python
的反射:根據字串執行函式,根據字串匯入包
go
是靜態語言,反射就是 go
提供的一種機制,在編譯時不知道型別的情況下可以做如下的事情
- 更新變數
- 執行時檢視值
- 呼叫方法
- 對他們的佈局進行操作
使用反射的兩個經典場景
-
你編寫的這個函式,還不知道傳給你的型別具體是什麼,可能是還沒約定好,也可能是傳入的型別很多
-
希望通過使用者的輸入來決定呼叫哪個函式(根據字串呼叫方法),動態執行函式
2、反射的基礎資料型別

3、Type
reflect.Type
是一個介面型別,用於獲取變數型別相關的資訊,可通過 reflect.TypeOf
函式獲取某個變數的型別資訊
原始碼 go/src/reflect/type.go
type Type interface { Align() int FieldAlign() int Method(int) Method // 第 i 個方法 MethodByName(string) (Method, bool) // 根據名稱獲取方法 NumMethod() int // 方法的個數 Name() string // 獲取結構體名稱 PkgPath() string // 包路徑 Size() uintptr // 佔用記憶體的大小 String() string // 獲取字串表述 Kind() Kind // 資料型別 Implements(u Type) bool // 判斷是否實現了某介面 AssignableTo(u Type) bool // 能否賦給另外一種型別 ConvertibleTo(u Type) bool // 能否轉換為另外一種型別 Comparable() bool Bits() int ChanDir() ChanDir IsVariadic() bool Elem() Type // 解析指標(指標型別轉為普通型別) Field(i int) StructField // 第i個成員 FieldByIndex(index []int) StructField // 根據index路徑獲取巢狀成員 FieldByName(name string) (StructField, bool) // 根據名稱獲取成員 FieldByNameFunc(match func(string) bool) (StructField, bool) In(i int) Type Key() Type Len() int // 容器的長度 NumField() int NumIn() int // 輸出引數的個數 NumOut() int // 返回引數的個數 Out(i int) Type common() *rtype uncommon() *uncommonType }
4、Value
reflect.Value
是一個結構體型別,用於獲取變數值的資訊,可通過 reflect.ValueOf
函式獲取修改原始資料型別(某個變數)的值資訊
原始碼 go/src/reflect/value.go
type Value struct { // 代表的資料型別 typ *rtype // 指向原始資料的指標 ptr unsafe.Pointer }
5、反射三大定律
interface
型別有個 value
, type
對,而反射就是檢查 interface
的這個 value
, type
對的
具體一點說就是 Go
提供一組方法提取 interface
的 value
,提供另一組方法提取 interface
的 type
-
reflect.Type
提供一組介面處理interface
的型別,即value
,type
中的type
-
reflect.Value
提供一組介面處理interface
的值,即value
,type
中的value
5.1 反射第一定律
反射第一定律:反射可以將 interface
型別變數轉換成反射物件
如何通過反射獲取一個變數的值和型別
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 t := reflect.TypeOf(x) //t is reflext.Type fmt.Println("type:", t) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) v := reflect.ValueOf(x) //v is reflext.Value fmt.Println("value:", v) }
程式輸出
type: float64 kind is float64: true value: 3.4
反射是針對 interface
型別變數的,其中 TypeOf()
和 ValueOf()
接受的引數都是 interface{}
型別的,也即 x
值是被轉成了 interface
傳入的
5.2 反射第二定律
反射第二定律:反射可以將反射物件還原成 interface
物件
之所以叫’反射’,反射物件與 interface
物件是可以互相轉化的
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 v := reflect.ValueOf(x) //v is reflext.Value var y float64 = v.Interface().(float64) fmt.Println("value:", y) }
物件 x
轉換成反射物件 v
, v
又通過 Interface()
介面轉換成 interface
物件, interface
物件通過 .(float64)
型別斷言獲取 float64
型別的值
5.3 反射第三定律
反射第三定律:反射物件可修改, value
值必須是可設定的
通過反射可以將 interface
型別變數轉換成反射物件,可以使用該反射物件設定其持有的值
package main import ( "reflect" ) func main() { var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic. }
通過反射物件v設定新值,會出現 panic
panic: reflect: reflect.Value.SetFloat using unaddressable value
錯誤原因即是 v
是不可修改的。
反射物件是否可修改取決於其所儲存的值,回想一下函式傳參時是傳值還是傳址就不難理解上例中為何失敗了。
上面例子傳入 reflect.ValueOf()
函式的其實是 x
的值,而非 x
本身。即通過 v
修改其值是無法影響 x
的,也即是無效的修改,所以 golang
會報錯
想到此處,即可明白,如果構建 v
時使用 x
的地址就可實現修改了,但此時 v
代表的是指標地址,我們要設定的是指標所指向的內容,也即我們想要修改的是 *v
。 通過 v
修改 x
的值?
reflect.Value
提供了 Elem()
方法,可以獲得指標向指向的 value
package main import ( "reflect" "fmt" ) func main() { var x float64 = 3.4 v := reflect.ValueOf(&x) v.Elem().SetFloat(7.1) fmt.Println("x :", v.Elem().Interface()) }
輸出為:
x : 7.1
6、反射常用的API
6.1 獲取type型別
typeUser := reflect.TypeOf(&User{}) //通過TypeOf()得到Type型別 fmt.Println(typeUser) //*User fmt.Println(typeUser.Elem()) //User fmt.Println(typeUser.Name()) //空字串 fmt.Println(typeUser.Elem().Name()) //User,不帶包名的類名稱 fmt.Println(typeUser.Kind()) //ptr fmt.Println(typeUser.Elem().Kind()) //struct fmt.Println(typeUser.Kind() == reflect.Ptr) fmt.Println(typeUser.Elem().Kind() == reflect.Struct)
6.2 獲取Field資訊
typeUser := reflect.TypeOf(User{}) //需要用struct的Type,不能用指標的Type fieldNum := typeUser.NumField() //成員變數的個數 for i := 0; i < fieldNum; i++ { field := typeUser.Field(i) fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i, field.Name, //變數名稱 field.Offset, //相對於結構體首地址的記憶體偏移量,string型別會佔據16個位元組 field.Anonymous, //是否為匿名成員 field.Type, //資料型別,reflect.Type型別 field.IsExported(), //包外是否可見(即是否以大寫字母開頭) field.Tag.Get("json")) //獲取成員變數後面``裡面定義的tag //可以通過FieldByName獲取Field if nameField, ok := typeUser.FieldByName("Name"); ok { fmt.Printf("Name is exported %t\n", nameField.IsExported()) } //也可以根據FieldByIndex獲取Field thirdField := typeUser.FieldByIndex([]int{2}) //引數是個slice,因為有struct巢狀的情況 fmt.Printf("third field name %s\n", thirdField.Name) }
6.3 獲取method資訊
typeUser := reflect.TypeOf(common.User{}) methodNum := typeUser.NumMethod() //成員方法的個數。接收者為指標的方法【不】包含在內 for i := 0; i < methodNum; i++ { method := typeUser.Method(i) fmt.Println(method.Type) // 會輸出函式完整的簽名,其中輸入引數會將結構體本身也作為輸入引數,因此引數個數多一個 fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported()) } fmt.Println() if method, ok := typeUser.MethodByName("Examine2"); ok { // 根據方法名獲取 fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported()) } typeUser2 := reflect.TypeOf(&common.User{}) methodNum = typeUser2.NumMethod() //成員方法的個數。接收者為指標或值的方法都包含在內,也就是說值實現的方法指標也實現了(反之不成立) for i := 0; i < methodNum; i++ { method := typeUser2.Method(i) fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported()) }
6.4 獲取函式資訊
typeFunc := reflect.TypeOf(Add) //獲取函式型別 fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func) argInNum := typeFunc.NumIn() //輸入引數的個數 argOutNum := typeFunc.NumOut() //輸出引數的個數 for i := 0; i < argInNum; i++ { argTyp := typeFunc.In(i) fmt.Printf("第%d個輸入引數的型別%s\n", i, argTyp) } for i := 0; i < argOutNum; i++ { argTyp := typeFunc.Out(i) fmt.Printf("第%d個輸出引數的型別%s\n", i, argTyp) }
6.5 賦值和轉換關係
- type1.AssignableTo(type2) // type1代表的型別是否可以賦值給type2代表的型別
- type1.ConvertibleTo(type2)) // type1代表的型別是否可以轉換成type2代表的型別
- java的反射可以獲取繼承關係,而go語言不支援繼承,所以必須是相同的型別才能AssignableTo和ConvertibleTo
示例
u := reflect.TypeOf(User{}) t := reflect.TypeOf(Student{}) //Student內部嵌套了User u2 := reflect.TypeOf(User{}) //false false fmt.Println(t.AssignableTo(u)) //t代表的型別是否可以賦值給u代表的型別 fmt.Println(t.ConvertibleTo(u)) //t代表的型別是否可以轉換成u代表的型別 //false false fmt.Println(u.AssignableTo(t)) fmt.Println(u.ConvertibleTo(t)) //true true fmt.Println(u.AssignableTo(u2)) fmt.Println(u.ConvertibleTo(u2))
6.6 是否實現介面
//通過reflect.TypeOf((*<interface>)(nil)).Elem()獲得介面型別。因為People是個介面不能建立例項,所以把nil強制轉為*common.People型別 typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem() // 可以將nil理解成People指標的一個例項 fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface) t1 := reflect.TypeOf(common.User{}) t2 := reflect.TypeOf(&common.User{}) //User的值型別實現了介面,則指標型別也實現了介面;但反過來不行(把Think的接收者改為*User試試) fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople)) // false fmt.Printf("t2 implements People interface %t\n", t2.Implements(typeOfPeople)) // true
6.7 value和其他型別的互換
//原始型別轉為Value iValue := reflect.ValueOf(1) sValue := reflect.ValueOf("hello") userPtrValue := reflect.ValueOf(&common.User{ Id: 7, Name: "傑克遜", Weight: 65, Height: 1.68, }) fmt.Println(iValue) //1 fmt.Println(sValue) //hello fmt.Println(userPtrValue) //&{7 傑克遜 65 1.68} //Value轉為Type iType := iValue.Type() sType := sValue.Type() userType := userPtrValue.Type() //在Type和相應Value上呼叫Kind()結果一樣的 fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind()) //true true fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind()) //true true fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind()) //true true true //指標Value和非指標Value互相轉換 userValue := userPtrValue.Elem() //Elem() 指標Value轉為非指標Value fmt.Println(userValue.Kind(), userPtrValue.Kind()) //struct ptr userPtrValue3 := userValue.Addr() //Addr() 非指標Value轉為指標Value fmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr //轉為原始型別 //通過Interface()函式把Value轉為interface{},再從interface{}強制型別轉換,轉為原始資料型別 //或者在Value上直接呼叫Int()、String()等一步到位 fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int()) fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String()) user := userValue.Interface().(common.User) fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height) user2 := userPtrValue.Interface().(*common.User) fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)
6.8 value判斷空值的三種情況
pointer
、 channel
、 func
、 interface
、 map
、 slice
的預先宣告都是 nil
var i interface{} //介面沒有指向具體的值 v := reflect.ValueOf(i) fmt.Printf("v持有值 %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid) // false var user *common.User = nil v = reflect.ValueOf(user) //Value指向一個nil if v.IsValid() { fmt.Printf("v持有的值是nil %t\n", v.IsNil()) //呼叫IsNil()前先確保IsValid(),否則會panic // true } var u common.User //只宣告,裡面的值都是0值 v = reflect.ValueOf(u) if v.IsValid() { fmt.Printf("v持有的值是對應型別的0值 %t\n", v.IsZero()) //呼叫IsZero()前先確保IsValid(),否則會panic // true }
參考: https://go.dev/blog/laws-of-reflection
See you ~
- Gradle打包工具入門
- 服務網格和Istio初識-續
- 服務網格和Istio初識
- Golang與非對稱加密
- ack叢集Terway網路場景下的vSwitch擴容
- Golang與對稱加密
- 基於ack k8s叢集排程的方案設計
- 基於Dockerfile構建容器映象的最佳實踐
- Golang反射-下篇
- Golang反射-上篇
- Azure DevOps的使用入門
- Golang介面型別-下篇
- Golang介面型別-上篇
- 基於Python實現原生的登入驗證碼
- Golang開發命令列工具之flag包的使用
- Golang檔案操作-下篇
- k8s環境下處理容器時間問題的多種姿勢
- Golang基準測試
- 淺談Prometheus的資料儲存
- Golang單元測試