聲明: 轉(zhuǎn)載自golang reflect包,反射學(xué)習(xí)與實踐
Go 語言反射的三大法則罢坝,其中包括:
- 從 interface{} 變量可以反射出反射對象廓握;
- 從反射對象可以獲取 interface{} 變量;
- 要修改反射對象嘁酿,其值必須可設(shè)置隙券;
從反射對象到接口值的過程就是從接口值到反射對象的鏡面過程,兩個過程都需要經(jīng)歷兩次轉(zhuǎn)換:
- 從接口值到反射對象:
- 從基本類型到接口類型的類型轉(zhuǎn)換痹仙;
- 從接口類型到反射對象的轉(zhuǎn)換是尔;
- 從反射對象到接口值:
- 反射對象轉(zhuǎn)換成接口類型;
- 通過顯式類型轉(zhuǎn)換變成原始類型开仰;
Type拟枚,Value
反射包中的所有方法基本都是圍繞著Type
和Value
這兩個類型設(shè)計的薪铜。我們通過reflect.TypeOf
、reflect.ValueOf
可以將一個普通的變量轉(zhuǎn)換成『反射』包中提供的Type
和Value
恩溅,隨后就可以使用反射包中的方法對它們進行復(fù)雜的操作隔箍。
類型Type
是反射包定義的一個接口,我們可以使用 reflect.TypeOf
函數(shù)獲取任意變量的的類型脚乡,Type
接口中定義了一些有趣的方法蜒滩,MethodByName
可以獲取當(dāng)前類型對應(yīng)方法的引用、Implements
可以判斷當(dāng)前類型是否實現(xiàn)了某個接口:
type Type interface {
// 變量的內(nèi)存對齊奶稠,返回 rtype.align
Align() int
// struct 字段的內(nèi)存對齊俯艰,返回 rtype.fieldAlign
FieldAlign() int
// 根據(jù)傳入的 i,返回方法實例锌订,表示類型的第 i 個方法
Method(int) Method
// 根據(jù)名字返回方法實例竹握,這個比較常用
MethodByName(string) (Method, bool)
// 返回類型方法集中可導(dǎo)出的方法的數(shù)量
NumMethod() int
// 只返回類型名,不含包名
Name() string
// 返回導(dǎo)入路徑辆飘,即 import 路徑
PkgPath() string
// 返回 rtype.size 即類型大小啦辐,單位是字節(jié)數(shù)
Size() uintptr
// 返回類型名字,實際就是 PkgPath() + Name()
String() string
// 返回 rtype.kind蜈项,描述一種基礎(chǔ)類型
Kind() Kind
// 檢查當(dāng)前類型有沒有實現(xiàn)接口 u
Implements(u Type) bool
// 檢查當(dāng)前類型能不能賦值給接口 u
AssignableTo(u Type) bool
// 檢查當(dāng)前類型能不能轉(zhuǎn)換成接口 u 類型
ConvertibleTo(u Type) bool
// 檢查當(dāng)前類型能不能做比較運算芹关,其實就是看這個類型底層有沒有綁定 typeAlg 的 equal 方法。
// 打捉糇洹侥衬!不要去搜 typeAlg 是什么,不然你會陷進去的常侦!先把本文看完浇冰。
Comparable() bool
// 返回類型的位大小,但不是所有類型都能調(diào)這個方法聋亡,不能調(diào)的會 panic
Bits() int
// 返回 channel 類型的方向肘习,如果不是 channel,會 panic
ChanDir() ChanDir
// 返回函數(shù)類型的最后一個參數(shù)是不是可變數(shù)量的坡倔,"..." 就這樣的漂佩,同樣,如果不是函數(shù)類型罪塔,會 panic
IsVariadic() bool
// 返回所包含元素的類型投蝉,只有 Array, Chan, Map, Ptr, Slice 這些才能調(diào),其他類型會 panic征堪。
// 這不是廢話嗎瘩缆。。其他類型也沒有包含元素一說佃蚜。
Elem() Type
// 返回 struct 類型的第 i 個字段庸娱,不是 struct 會 panic着绊,i 越界也會 panic
Field(i int) StructField
// 跟上邊一樣,不過是嵌套調(diào)用的熟尉,比如 [1, 2] 就是說返回當(dāng)前 struct 的第1個struct 的第2個字段归露,適用于 struct 本身嵌套的類型
FieldByIndex(index []int) StructField
// 按名字找 struct 字段,第二個返回值 ok 表示有沒有
FieldByName(name string) (StructField, bool)
// 按函數(shù)名找 struct 字段斤儿,因為 struct 里也可能有類型是 func 的嘛
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 返回函數(shù)第 i 個參數(shù)的類型剧包,不是 func 會 panic
In(i int) Type
// 返回 map 的 key 的類型,不是 map 會 panic
Key() Type
// 返回 array 的長度往果,不是 array 會 panic
Len() int
// 返回 struct 字段數(shù)量疆液,不是 struct 會 panic
NumField() int
// 返回函數(shù)的參數(shù)數(shù)量,不是 func 會 panic
NumIn() int
// 返回函數(shù)的返回值數(shù)量棚放,不是 func 會 panic
NumOut() int
// 返回函數(shù)第 i 個返回值的類型枚粘,不是 func 會 panic
Out(i int) Type
}
反射包中 Value
的類型與 Type
不同,它被聲明成了結(jié)構(gòu)體飘蚯。這個結(jié)構(gòu)體沒有對外暴露的字段,但是提供了獲取或者寫入數(shù)據(jù)的方法:
type Value struct {
// 反射出來此值的類型福也,rtype 是啥往上看局骤,但可別弄錯了,這 typ 是未導(dǎo)出的暴凑,從外部調(diào)不到 Type 接口的方法
typ *rtype
// 數(shù)據(jù)形式的指針值
ptr unsafe.Pointer
// 保存元數(shù)據(jù)
flag
}
// 前提 v 是一個 func峦甩,然后調(diào)用 v,并傳入 in 參數(shù)现喳,第一個參數(shù)是 in[0]凯傲,第二個是 in[1],以此類推
func (v Value) Call(in []Value) []Value
// 返回 v 的接口值或者指針
func (v Value) Elem() Value
// 前提 v 是一個 struct嗦篱,返回第 i 個字段冰单,這個主要用于遍歷
func (v Value) Field(i int) Value
// 前提 v 是一個 struct,根據(jù)字段名直接定位返回
func (v Value) FieldByName(name string) Value
// 前提 v 是 Array, Slice, String 之一灸促,返回第 i 個元素诫欠,主要也是用于遍歷,注意不能越界
func (v Value) Index(i int) Value
// 判斷 v 是不是 nil浴栽,只有 chan, func, interface, map, pointer, slice 可以用荒叼,其他類型會 panic
func (v Value) IsNil() bool
// 判斷 v 是否合法,如果返回 false典鸡,那么除了 String() 以外的其他方法調(diào)用都會 panic被廓,事前檢查是必要的
func (v Value) IsValid() bool
// 前提 v 是個 map,返回對應(yīng) value
func (v Value) MapIndex(key Value)
// 前提 v 是個 map萝玷,返回所有 key 組成的一個 slice
func (v Value) MapKeys() []Value
// 前提 v 是個 struct嫁乘,返回字段個數(shù)
func (v Value) NumField() int
// 賦值
func (v Value) Set(x Value)
// 類型
func (v Value) Type() Type
// 等等...
實踐
-
遍歷一個結(jié)構(gòu)體的字段以及對應(yīng)的值
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Sex string
Age int
PhoneNum string
School string
City string
}
func main() {
p1 := Person{
Name: "tom",
Sex: "male",
Age: 10,
PhoneNum: "1000000",
School: "spb-kindergarden",
City: "cq",
}
rv := reflect.ValueOf(p1)
rt := reflect.TypeOf(p1)
if rv.Kind() == reflect.Struct {
for i := 0; i < rt.NumField(); i++ {
//按順序遍歷
fmt.Printf("field:%+v,value:%+v\n", rt.Field(i).Name, rv.Field(i))
}
}
}
-
若知道字段名英遭,直接去取該字段
rv := reflect.ValueOf(p1)
rt := reflect.TypeOf(p1)
//可以直接取想要的字段
//reflect的type interface,F(xiàn)ieldByName方法會返回字段信息以及是否有該字段亦渗;
if f, ok := rt.FieldByName("Age"); ok {
fmt.Printf("field:%+v,value:%+v\n", f.Name, rv.FieldByName("Age"))
}
字段信息是一個結(jié)構(gòu)體挖诸,它描述了該字段的下列屬性:
// A StructField describes a single field in a struct.
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
-
判斷一個變量的類型
rv := reflect.ValueOf(p1)
rt := reflect.TypeOf(p1)
fmt.Printf("kind is %+v\n", rt.Kind())
fmt.Printf("kind is %+v\n", rv.Kind())
type
和value
的Kind()
方法都可以返回該變量的類型,不過若取得value
后發(fā)現(xiàn)其是一個零值法精,那么會返回Kind
為Invalid
// Kind returns v's Kind.
// If v is the zero Value (IsValid returns false), Kind returns Invalid.
func (v Value) Kind() Kind {
return v.kind()
}
reflect
的Kind
一共有27種類型多律,基本攬括了所有g(shù)olang中的類型
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
)
-
獲取tag的值
type TagTest struct {
Name string `json:"name_json"`
Age int `json:"age_json"`
}
t := TagTest{Name: "tom", Age: 10}
rtt := reflect.TypeOf(t)
//rtv := reflect.ValueOf(t)
for i := 0; i < rtt.NumField(); i++ {
field := rtt.Field(i)
if json, ok := field.Tag.Lookup("json"); ok {
fmt.Printf("tag is %+v, value is %+v\n", json, field.Tag.Get("json"))
}
}
注意,field.Tag.Lookup()
和field.Tag.Get()
方法都是取tag
的值搂蜓,只不過Lookup
會用第二個返回值返回是否存在這個tag
狼荞,而Get
方法若不存在這個tag
會返回一個空字符串
-
動態(tài)調(diào)用方法
*T
有方法Add
type T struct{}
func (t *T) Add(a, b int) {
fmt.Printf("a + b is %+v\n", a+b)
}
動態(tài)調(diào)用
funcName := "Add"
typeT := &T{}
a := reflect.ValueOf(1)
b := reflect.ValueOf(2)
in := []reflect.Value{a, b}
reflect.ValueOf(typeT).MethodByName(funcName).Call(in)
-
動態(tài)調(diào)用含返回值的方法
func (t *T) AddRetErr(a, b int) (int, error) {
if a+b < 10 {
return a + b, errors.New("total lt 10")
}
return a + b, nil
}
調(diào)用
funcName = "AddRetErr"
ret := reflect.ValueOf(typeT).MethodByName(funcName).Call(in)
fmt.Printf("ret is %+v\n", ret)
for i := 0; i < len(ret); i++ {
fmt.Printf("ret index:%+v, type:%+v, value:%+v\n", i, ret[i].Kind(), ret[i].Interface())
}
這里的ret[i].Kind()
,若非基礎(chǔ)類型帮碰,會得到interface
如果err
不是nil
相味,
if v, ok := ret[1].Interface().(error); ok {
fmt.Printf("v is %+v\n", v)
}
類型斷言會成功,可以用這種方式去判斷返回的error
是否為空
-
通過反射修改值
不是所有的反射值都可以修改殉挽。對于一個反射值是否可以修改丰涉,可以通過CanSet()
進行檢查。
要修改值斯碌,必須滿足:
- 可以尋址
- 可尋址的類型:
- 指針指向的具體元素
- slice的元素
- 結(jié)構(gòu)體指針的字段
- 數(shù)組指針的元素
1. 指針指向的具體元素
需要三步:
- 取地址:
v := reflect.ValueOf(&x)
- 判斷
v.Elem()
是否可以設(shè)值 - 給v.Elem()設(shè)置具體值
ta := 10
vta := reflect.ValueOf(&ta)
if vta.Elem().CanSet() {
vta.Elem().Set(reflect.ValueOf(11))
}
fmt.Println("cant set")
fmt.Printf("vta is :%+v\n", vta.Elem())
2. slice中的元素
ts := []int{1, 2, 3}
tsV := reflect.ValueOf(ts)
if tsV.Index(0).CanSet() {
tsV.Index(0).Set(reflect.ValueOf(10))
}
fmt.Printf("ts is %+v\n", ts)
//輸出:ts is [10 2 3]
3. 結(jié)構(gòu)體指針的字段
t1 := TagTest{}
tV := reflect.ValueOf(t)
//結(jié)構(gòu)體指針
t1V := reflect.ValueOf(&t1)
fmt.Printf("tV:%+v\n", tV)
for i := 0; i < tV.NumField(); i++ {
val := tV.Field(i)
t1V.Elem().Field(i).Set(val)
}
fmt.Printf("t1 is %+v\n", t1)
4. 數(shù)組指針的元素
tsA := [3]int{1, 2, 3}
tsAv := reflect.ValueOf(&tsA)
if tsAv.Elem().Index(0).CanSet() {
tsAv.Elem().Index(0).Set(reflect.ValueOf(10))
}
fmt.Printf("tsA is %+v\n", tsA)
注意與slice
中元素修改的區(qū)別