轉載請注明出處:Golang 學習筆記(11)—— 反射
介紹
反射是程序執行時檢查其所擁有的結構。尤其是類型的一種能力。這是元編程的一種形式。它同一時候也是造成混淆的重要來源。
每一個語言的反射模型都不同(同一時候很多語言根本不支持反射)。本節將試圖明白解釋在 Go 中的反射是怎樣工作的
實現
package
import "reflect"
Type和Value
首先,reflect包有兩個數據類型我們必須知道,一個是Type,一個是Value。
Type就是定義的類型的一個數據類型,Value是值的類型
下面分別對Type和Value做個簡單介紹一下
Type
Type類型用來表示一個go類型。
不是所有go類型的Type值都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知類型的分類。調用該分類不支持的方法會導致運行時的panic。
獲取Type對象的方法:
func TypeOf(i interface{}) Type
例如:
str := "this is string"
type := reflect.TypeOf(str)
Type中常用的方法:
- Kind() Kind
Kind返回該接口的具體分類
Kind代表Type類型值表示的具體分類。零值表示非法分類。
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
Name() string
Name返回該類型在自身包內的類型名,如果是未命名類型會返回""PkgPath() string
PkgPath返回類型的包路徑,即明確指定包的import路徑,如"encoding/base64"
如果類型為內建類型(string, error)或未命名類型(*T, struct{}, []int),會返回""NumField() int
返回struct類型的字段數(匿名字段算作一個字段),如非結構體類型將panicField(i int) StructField
返回struct類型的第i個字段的類型,如非結構體或者i不在[0, NumField())內將會panicFieldByIndex(index []int) StructField
返回索引序列指定的嵌套字段的類型,
等價于用索引中每個值鏈式調用本方法,如非結構體將會panicFieldByName(name string) (StructField, bool)
返回該類型名為name的字段(會查找匿名字段及其子字段),
布爾值說明是否找到,如非結構體將panictype StructField
type StructField struct {
// Name是字段的名字。PkgPath是非導出字段的包路徑,對導出字段該字段為""。
Name string
PkgPath string
Type Type // 字段的類型
Tag StructTag // 字段的標簽
Offset uintptr // 字段在結構體中的字節偏移量
Index []int // 用于Type.FieldByIndex時的索引切片
Anonymous bool // 是否匿名字段
}
StructField類型描述結構體中的一個字段的信息。
NumMethod() int
返回該類型的方法集中方法的數目
匿名字段的方法會被計算;主體類型的方法會屏蔽匿名字段的同名方法;
匿名字段導致的歧義方法會濾除Method(int) Method
返回該類型方法集中的第i個方法,i不在[0, NumMethod())范圍內時,將導致panic
對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態
對接口類型,返回值的Type字段描述方法的簽名,Func字段為nilMethodByName(string) (Method, bool)
根據方法名返回該類型方法集中的方法,使用一個布爾值說明是否發現該方法
對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態
對接口類型,返回值的Type字段描述方法的簽名,Func字段為niltype Method
type Method struct {
// Name是方法名。PkgPath是非導出字段的包路徑,對導出字段該字段為""。
// 結合PkgPath和Name可以從方法集中指定一個方法。
Name string
PkgPath string
Type Type // 方法類型
Func Value // 方法的值
Index int // 用于Type.Method的索引
}
Method代表一個方法。
Value
Value為go值提供了反射接口。
不是所有go類型值的Value表示都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知該值的分類。調用該分類不支持的方法會導致運行時的panic。
Value類型的零值表示不持有某個值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回<invalid Value>
,所有其它方法都會panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。
如果某個go類型值可以安全的用于多線程并發操作,它的Value表示也可以安全的用于并發。
獲取Value對象的方法:
func ValueOf(i interface{}) Value
ValueOf返回一個初始化為i接口保管的具體值的Value,ValueOf(nil)返回Value零值。
例如:
str := "this is string"
val := reflect.ValueOf(str)
** Value中常用的方法:**
- Kind()
func (v Value) Kind() Kind
Kind返回v持有的值的分類,如果v是Value零值,返回值為Invalid
- func (Value) IsValid
func (v Value) IsValid() bool
IsValid返回v是否持有一個值。如果v是Value零值會返回假,此時v除了IsValid、String、Kind之外的方法都會導致panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。
- func (Value) IsNil
func (v Value) IsNil() bool
IsNil報告v持有的值是否為nil。v持有的值的分類必須是通道、函數、接口、映射、指針、切片之一;否則IsNil函數會導致panic。注意IsNil并不總是等價于go語言中值與nil的常規比較。例如:如果v是通過使用某個值為nil的接口調用ValueOf函數創建的,v.IsNil()返回真,但是如果v是Value零值,會panic。
- func (Value) Type
func (v Value) Type() Type
返回v持有的值的類型的Type表示。
- func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。
- func (Value) NumField
func (v Value) NumField() int
返回v持有的結構體類型值的字段數,如果v的Kind不是Struct會panic
- func (Value) Field
func (v Value) Field(i int) Value
返回結構體的第i個字段(的Value封裝)。如果v的Kind不是Struct或i出界會panic
- func (Value) FieldByIndex
func (v Value) FieldByIndex(index []int) Value
返回索引序列指定的嵌套字段的Value表示,等價于用索引中的值鏈式調用本方法,如v的Kind非Struct將會panic
- func (Value) FieldByName
func (v Value) FieldByName(name string) Value
返回該類型名為name的字段(的Value封裝)(會查找匿名字段及其子字段),如果v的Kind不是Struct會panic;如果未找到會返回Value零值。
- func (Value) NumMethod
func (v Value) NumMethod() int
返回v持有值的方法集的方法數目。
- func (Value) Method
func (v Value) Method(i int) Value
返回v持有值類型的第i個方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果i出界,或者v的持有值是接口類型的零值(nil),會panic。
- func (Value) MethodByName
func (v Value) MethodByName(name string) Value
返回v的名為name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果未找到該方法,會返回一個Value零值。
- func (Value) Call
func (v Value) Call(in []Value) []Value
Call方法使用輸入的參數in調用v持有的函數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。如果v持有值是可變參數函數,Call方法會自行創建一個代表可變參數的切片,將對應可變參數的值都拷貝到里面。
- func (Value) CallSlice
func (v Value) CallSlice(in []Value) []Value
CallSlice調用v持有的可變參數函數,會將切片類型的in[len(in)-1](的成員)分配給v的最后的可變參數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值,可變參數函數的可變參數位置提供一個切片并跟三個點號代表"解切片")。如果v的Kind不是Func或者v的持有值不是可變參數函數,會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。
- func (Value) Interface
func (v Value) Interface() (i interface{})
本方法返回v當前持有的值(表示為/保管在interface{}類型),等價于:
var i interface{} = (v's underlying value)
如果v是通過訪問非導出結構體字段獲取的,會導致panic。
另外還有很多類似這個函數的功能可以獲取value的值。
func (v Value) String() string
func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) Float() float64
func (v Value) Complex() complex128
func (v Value) Bytes() []byte
...
- func (Value) CanSet
func (v Value) CanSet() bool
如果v持有的值可以被修改,CanSet就會返回真。只有一個Value持有值可以被尋址同時又不是來自非導出字段時,它才可以被修改。如果CanSet返回假,調用Set或任何限定類型的設置函數(如SetBool、SetInt64)都會panic。
- 設置函數
func (v Value) SetBool(x bool)
func (v Value) SetInt(x int64)
func (v Value) SetUint(x uint64)
func (v Value) SetFloat(x float64)
func (v Value) SetComplex(x complex128)
func (v Value) SetBytes(x []byte)
func (v Value) SetString(x string)
...
例子
說了這么多,看下實例吧
對象:
package demo
import (
"fmt"
)
type Address struct{
City string
Area string
}
type Student struct{
Address
Name string
Age int
}
func (this Student) Say(){
fmt.Println("hello, i am ", this.Name, "and i am ", this.Age)
}
func (this Student) Hello(word string){
fmt.Println("hello", word, ". i am ", this.Name)
}
有關反射函數demo
package demo
import (
"fmt"
"reflect"
)
/*
獲取對象的信息
*/
func StructInfo(o interface{}){
//獲取對象的類型
t := reflect.TypeOf(o)
fmt.Println(t.Name(), "object type: ", t.Name())
if k := t.Kind(); k != reflect.Struct{
fmt.Println("the object is not a struct, but it is", t.Kind())
return
}
//獲取對象的值
v := reflect.ValueOf(o)
fmt.Println(t.Name(), "object value: ", v)
//獲取對象的字段
fmt.Println(t.Name(), "fields: ")
for i := 0; i < t.NumField(); i++{
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s:%v = %v \n", f.Name, f.Type, val)
//通過遞歸調用獲取子類型的信息
t1 := reflect.TypeOf(val)
if k := t1.Kind(); k == reflect.Struct{
StructInfo(val)
}
}
//獲取對象的函數
fmt.Println(t.Name(), "methods: ", t.NumMethod())
for i := 0; i < t.NumMethod(); i++{
m := t.Method(i)
fmt.Printf("%10s:%v \n", m.Name, m.Type)
}
}
/*
匿名字段的反射
*/
func Annoy(o interface{}){
t := reflect.TypeOf(o)
for i := 0; i < t.NumField(); i++{
f := t.Field(i)
fmt.Printf("%10s:%#v \n", f.Name, f)
}
}
/*
通過反射設置字段
*/
func ReflectSet(o interface{}){
v := reflect.ValueOf(o)
if v.Kind() == reflect.Ptr && !v.Elem().CanSet(){
fmt.Println("修改失敗")
return
}
v = v.Elem()
//獲取字段
f := v.FieldByName("Name")
if !f.IsValid(){
fmt.Println("修改失敗")
return
}
//設置值
if f.Kind() == reflect.String{
f.SetString("chairis")
}
}
/*
通過反射調用函數
*/
func ReflectMethod(o interface{}){
v := reflect.ValueOf(o)
//無參函數調用
m1:= v.MethodByName("Say")
m1.Call([]reflect.Value{})
//有參函數調用
m2 := v.MethodByName("Hello")
m2.Call([]reflect.Value{reflect.ValueOf("iris")})
}
上述代碼一共有以下這幾個函數:
StructInfo
:是用來說明type, 和value的使用方式的按照以下方式調用:
package main
import (
. "stu_demo/demo"
)
func main(){
stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
StructInfo(stu)
}
結果如下:
Annoy
: 用來展示匿名字段的函數。
照以下方式調用:
package main
import (
. "stu_demo/demo"
)
func main(){
stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
Annoy(stu)
}
結果如下:
ReflectSet
: 是用來實現通過反射來修改字段值的函數。
按照以下調用:
package main
import (
"fmt"
. "stu_demo/demo"
)
func main(){
stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
fmt.Println("before: ", stu)
ReflectSet(&stu)
fmt.Println("after: ", stu)
}
結果如下:
ReflectMethod
: 是用來實現通過反射調用函數的方法。
按照以下調用:
package main
import (
. "stu_demo/demo"
)
func main(){
stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
ReflectMethod(stu)
}
得以下結果:
源碼
完
轉載請注明出處:Golang 學習筆記(11)—— 反射
目錄
上一節:Golang 學習筆記(10)—— mysql操作
下一節:Golang 學習筆記(12)—— ORM實現