聲明: 轉(zhuǎn)載自golang reflect包,反射學(xué)習(xí)與實(shí)踐
Go 語(yǔ)言反射的三大法則,其中包括:
- 從 interface{} 變量可以反射出反射對(duì)象;
- 從反射對(duì)象可以獲取 interface{} 變量;
- 要修改反射對(duì)象,其值必須可設(shè)置;
從反射對(duì)象到接口值的過(guò)程就是從接口值到反射對(duì)象的鏡面過(guò)程,兩個(gè)過(guò)程都需要經(jīng)歷兩次轉(zhuǎn)換:
- 從接口值到反射對(duì)象:
- 從基本類型到接口類型的類型轉(zhuǎn)換;
- 從接口類型到反射對(duì)象的轉(zhuǎn)換;
- 從反射對(duì)象到接口值:
- 反射對(duì)象轉(zhuǎn)換成接口類型;
- 通過(guò)顯式類型轉(zhuǎn)換變成原始類型;
Type,Value
反射包中的所有方法基本都是圍繞著Type和Value這兩個(gè)類型設(shè)計(jì)的。我們通過(guò)reflect.TypeOf、reflect.ValueOf可以將一個(gè)普通的變量轉(zhuǎn)換成『反射』包中提供的Type和Value,隨后就可以使用反射包中的方法對(duì)它們進(jìn)行復(fù)雜的操作。
類型Type是反射包定義的一個(gè)接口,我們可以使用 reflect.TypeOf 函數(shù)獲取任意變量的的類型,Type 接口中定義了一些有趣的方法,MethodByName 可以獲取當(dāng)前類型對(duì)應(yīng)方法的引用、Implements 可以判斷當(dāng)前類型是否實(shí)現(xiàn)了某個(gè)接口:
type Type interface {
// 變量的內(nèi)存對(duì)齊,返回 rtype.align
Align() int
// struct 字段的內(nèi)存對(duì)齊,返回 rtype.fieldAlign
FieldAlign() int
// 根據(jù)傳入的 i,返回方法實(shí)例,表示類型的第 i 個(gè)方法
Method(int) Method
// 根據(jù)名字返回方法實(shí)例,這個(gè)比較常用
MethodByName(string) (Method, bool)
// 返回類型方法集中可導(dǎo)出的方法的數(shù)量
NumMethod() int
// 只返回類型名,不含包名
Name() string
// 返回導(dǎo)入路徑,即 import 路徑
PkgPath() string
// 返回 rtype.size 即類型大小,單位是字節(jié)數(shù)
Size() uintptr
// 返回類型名字,實(shí)際就是 PkgPath() + Name()
String() string
// 返回 rtype.kind,描述一種基礎(chǔ)類型
Kind() Kind
// 檢查當(dāng)前類型有沒有實(shí)現(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)前類型能不能做比較運(yùn)算,其實(shí)就是看這個(gè)類型底層有沒有綁定 typeAlg 的 equal 方法。
// 打??!不要去搜 typeAlg 是什么,不然你會(huì)陷進(jìn)去的!先把本文看完。
Comparable() bool
// 返回類型的位大小,但不是所有類型都能調(diào)這個(gè)方法,不能調(diào)的會(huì) panic
Bits() int
// 返回 channel 類型的方向,如果不是 channel,會(huì) panic
ChanDir() ChanDir
// 返回函數(shù)類型的最后一個(gè)參數(shù)是不是可變數(shù)量的,"..." 就這樣的,同樣,如果不是函數(shù)類型,會(huì) panic
IsVariadic() bool
// 返回所包含元素的類型,只有 Array, Chan, Map, Ptr, Slice 這些才能調(diào),其他類型會(huì) panic。
// 這不是廢話嗎。。其他類型也沒有包含元素一說(shuō)。
Elem() Type
// 返回 struct 類型的第 i 個(gè)字段,不是 struct 會(huì) panic,i 越界也會(huì) panic
Field(i int) StructField
// 跟上邊一樣,不過(guò)是嵌套調(diào)用的,比如 [1, 2] 就是說(shuō)返回當(dāng)前 struct 的第1個(gè)struct 的第2個(gè)字段,適用于 struct 本身嵌套的類型
FieldByIndex(index []int) StructField
// 按名字找 struct 字段,第二個(gè)返回值 ok 表示有沒有
FieldByName(name string) (StructField, bool)
// 按函數(shù)名找 struct 字段,因?yàn)?struct 里也可能有類型是 func 的嘛
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 返回函數(shù)第 i 個(gè)參數(shù)的類型,不是 func 會(huì) panic
In(i int) Type
// 返回 map 的 key 的類型,不是 map 會(huì) panic
Key() Type
// 返回 array 的長(zhǎng)度,不是 array 會(huì) panic
Len() int
// 返回 struct 字段數(shù)量,不是 struct 會(huì) panic
NumField() int
// 返回函數(shù)的參數(shù)數(shù)量,不是 func 會(huì) panic
NumIn() int
// 返回函數(shù)的返回值數(shù)量,不是 func 會(huì) panic
NumOut() int
// 返回函數(shù)第 i 個(gè)返回值的類型,不是 func 會(huì) panic
Out(i int) Type
}
反射包中 Value 的類型與 Type 不同,它被聲明成了結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體沒有對(duì)外暴露的字段,但是提供了獲取或者寫入數(shù)據(jù)的方法:
type Value struct {
// 反射出來(lái)此值的類型,rtype 是啥往上看,但可別弄錯(cuò)了,這 typ 是未導(dǎo)出的,從外部調(diào)不到 Type 接口的方法
typ *rtype
// 數(shù)據(jù)形式的指針值
ptr unsafe.Pointer
// 保存元數(shù)據(jù)
flag
}
// 前提 v 是一個(gè) func,然后調(diào)用 v,并傳入 in 參數(shù),第一個(gè)參數(shù)是 in[0],第二個(gè)是 in[1],以此類推
func (v Value) Call(in []Value) []Value
// 返回 v 的接口值或者指針
func (v Value) Elem() Value
// 前提 v 是一個(gè) struct,返回第 i 個(gè)字段,這個(gè)主要用于遍歷
func (v Value) Field(i int) Value
// 前提 v 是一個(gè) struct,根據(jù)字段名直接定位返回
func (v Value) FieldByName(name string) Value
// 前提 v 是 Array, Slice, String 之一,返回第 i 個(gè)元素,主要也是用于遍歷,注意不能越界
func (v Value) Index(i int) Value
// 判斷 v 是不是 nil,只有 chan, func, interface, map, pointer, slice 可以用,其他類型會(huì) panic
func (v Value) IsNil() bool
// 判斷 v 是否合法,如果返回 false,那么除了 String() 以外的其他方法調(diào)用都會(huì) panic,事前檢查是必要的
func (v Value) IsValid() bool
// 前提 v 是個(gè) map,返回對(duì)應(yīng) value
func (v Value) MapIndex(key Value)
// 前提 v 是個(gè) map,返回所有 key 組成的一個(gè) slice
func (v Value) MapKeys() []Value
// 前提 v 是個(gè) struct,返回字段個(gè)數(shù)
func (v Value) NumField() int
// 賦值
func (v Value) Set(x Value)
// 類型
func (v Value) Type() Type
// 等等...
實(shí)踐
-
遍歷一個(gè)結(jié)構(gòu)體的字段以及對(duì)應(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方法會(huì)返回字段信息以及是否有該字段;
if f, ok := rt.FieldByName("Age"); ok {
fmt.Printf("field:%+v,value:%+v\n", f.Name, rv.FieldByName("Age"))
}
字段信息是一個(gè)結(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
}
-
判斷一個(gè)變量的類型
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()方法都可以返回該變量的類型,不過(guò)若取得value后發(fā)現(xiàn)其是一個(gè)零值,那么會(huì)返回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的值,只不過(guò)Lookup會(huì)用第二個(gè)返回值返回是否存在這個(gè)tag,而Get方法若不存在這個(gè)tag會(huì)返回一個(gè)空字符串
-
動(dòng)態(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)
}
動(dòng)態(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)
-
動(dòng)態(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ǔ)類型,會(huì)得到interface
如果err不是nil,
if v, ok := ret[1].Interface().(error); ok {
fmt.Printf("v is %+v\n", v)
}
類型斷言會(huì)成功,可以用這種方式去判斷返回的error是否為空
-
通過(guò)反射修改值
不是所有的反射值都可以修改。對(duì)于一個(gè)反射值是否可以修改,可以通過(guò)CanSet()進(jìn)行檢查。
要修改值,必須滿足:
- 可以尋址
- 可尋址的類型:
- 指針指向的具體元素
- 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ū)別