Go語言入門-反射
概述
定義
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
反射(Reflection )在運行時程序檢查其自身結構的能力,特別是常用於類型中,能讓程序在運行時檢查和獲取對象的類型信息和內存信息。從而實現一種類似於動態類型的功能,同時反射也是實現元編程的 形式。因爲其的靈活性,也會帶來一些額外的問題。
實現
go語言中反射是通過reflect包來獲取程序執行過程中的反射信息。
反射建立在類型系統之上,每個變量都有具體的靜態類型和對應的值,同時類型也關聯了對應的接口,一個變量賦值給接口變量是接口變量也會存放變量的具體的值和變量的類型描述,因此go語言的反射與類型和接口有緊密的聯繫。
入口函數
go語言的中反射和其他語言反射有一定的差異,本文只探索一下go語言的反射。go語言提供了兩個入口函數
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value
以上兩個函數的參數列表是 i interface{} 空接口,也就是可以傳入所有類型的變量。
- 示例1-簡單演示
reflect.TypeOf
和reflect.ValueOf
方法
func main() {
var a float64 = 1.2
t := reflect.TypeOf(a)
fmt.Printf("type = %v\n", t.Name())
fmt.Printf("type = %v\n", t)
v := reflect.ValueOf(a)
fmt.Printf("type = %v\n", v)
}
/**
output:
type = float64
type = float64
type = 1.2
*/
應用
reflect.TypeOf-獲取類型對象
類型和種類
在反射中類型是Type,除了Type以外還增加了一個粒度的劃分Kind。Type表示實際的類型,Kind表示構建Type的基礎的類別(底層類型)。當我們需要某些對象是不是指針、接口體類型是,我們就可以使用Kind來判斷。
- Kind一般以下幾種:
// A Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.
type Kind uint
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
)
可以看出Kind本身也是一種類型,起底層的種類是unit8,而類型是Kind,Kind的範圍是有限的由以上常量表示不同的類型,涵蓋了go語言中的所有的內置類型。需要注意的是
Invalid Kind = iota
不是一個有效類型。可以理解爲邊界值。
- 示例2-簡單演示Type和Kind
import (
"fmt"
"reflect"
)
type MyInt int
type MyFloat float64
type MyStruct struct {a,b int}
type MyFunc func(a, b int) error
type MyFunc2 func(a, b int) (int, error)
type MyMap map[int]string
type MyMap2 map[string]string
type MyInterface interface {hello()}
type MyInterface2 interface {}
// 別名
type MyStructAlias = MyStruct
func main() {
var myInt MyInt
var myFloat MyFloat
var myStruct MyStruct
var myFunc MyFunc
var myFunc2 MyFunc2
var myMap MyMap
var myMap2 MyMap2
var myStructAlias MyStructAlias
//var myInterface MyInterface
//var myInterface2 MyInterface2
fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myInt).Name(), reflect.TypeOf(myInt).Kind())
fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myFloat).Name(), reflect.TypeOf(myFloat).Kind())
fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myStruct).Name(), reflect.TypeOf(myStruct).Kind())
showReflectTypeAndKind(myFunc)
showReflectTypeAndKind(myFunc2)
showReflectTypeAndKind(myMap)
showReflectTypeAndKind(myMap2)
//類型別名的Type還是原來的Type
showReflectTypeAndKind(myStructAlias)
//showReflectTypeAndKind(myInterface)
//showReflectTypeAndKind(myInterface2)
}
func showReflectTypeAndKind(i interface{}) {
fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(i).Name(), reflect.TypeOf(i).Kind())
}
/**
output:
type=[MyInt] kind=[int]
type=[MyFloat] kind=[float64]
type=[MyStruct] kind=[struct]
type=[MyFunc] kind=[func]
type=[MyFunc2] kind=[func]
type=[MyMap] kind=[map]
type=[MyMap2] kind=[map]
type=[MyStruct] kind=[struct]
*/
構建基礎的複覈類型
reflect包中提供了一下類型方法
type Type
func ArrayOf(count int, elem Type) Type
func ChanOf(dir ChanDir, t Type) Type
func FuncOf(in, out []Type, variadic bool) Type
func MapOf(key, elem Type) Type
func PtrTo(t Type) Type
func SliceOf(t Type) Type
func StructOf(fields []StructField) Type
func TypeOf(i interface{}) Type
- 示例3-反射構建複合類型
//通過反射構建數組
func initAarryByX(i interface{}, n int)(result reflect.Type){
//通過reflect.ArrayOf構建數組類型
result = reflect.ArrayOf(n, reflect.TypeOf(i))
return
}
func main() {
i := 10
//a是reflect.type類型是構建出的類型
a := initAarryByX(i, 5)
//[5]int
fmt.Println(a)
}
/**
output:
[5]int
*/
反射構建出複合、引用類型
reflect包中提供以下函數構建對象:
// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PtrTo(typ).
func New(typ Type) Value
// NewAt returns a Value representing a pointer to a value of the
// specified type, using p as that pointer.
func NewAt(typ Type, p unsafe.Pointer) Value
- 示例4 - 構建複合類型(數組)並創建對象
//通過反射構建數組
func initAarryByX(i interface{}, n int)(result reflect.Type){
//通過reflect.ArrayOf構建數組類型
result = reflect.ArrayOf(n, reflect.TypeOf(i))
return
}
func main() {
i := 10
//a是reflect.type類型是構建出的類型
a := initAarryByX(i, 5)
//[5]int
fmt.Println(a)
//構建對象
v1 := reflect.New(a)
//
fmt.Println(v1)
}
/**
output:
[5]int
&[0 0 0 0 0]
*/
反射構建引用類型的實例
reflect包中提供以下函數對反射構建的對象初始化
// MakeSlice creates a new zero-initialized slice value
// for the specified slice type, length, and capacity.
func MakeSlice(typ Type, len, cap int) Value
// MakeChan creates a new channel with the specified type and buffer size.
func MakeChan(typ Type, buffer int) Value
// MakeMap creates a new map with the specified type.
func MakeMap(typ Type) Value
// MakeFunc returns a new function of the given Type
// that wraps the function fn. When called, that new function
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
- 示例4 簡單演示 reflect.MapOf reflect.MakeMap SetMapIndex的使用
func main() {
//ty := reflect.MapOf(reflect.TypeOf("sd").(reflect.Type), reflect.TypeOf("sd").(reflect.Type))
//生成一個map類型,key的類似通過"aa"獲取到類型爲string value的類型通過"bb"獲取的類型爲string
ty := initReflectMap("aa", "bb")
//map[string]string
fmt.Println(ty)
//創建map
map1 := reflect.MakeMap(ty)
//設置map的key和value
map1.SetMapIndex(reflect.ValueOf("hello"), reflect.ValueOf("world"))
//轉換構建出的類型reflect.Value到map[string]string
map2 := map1.Interface().(map[string]string)
//打印
fmt.Println(map1, reflect.TypeOf(map1))
//打印
fmt.Println(map2, reflect.TypeOf(map2))
}
/**
output:
map[string]string
map[hello:world] reflect.Value
map[hello:world] map[string]string
*/
reflect.ValueOf
reflect.ValueOf-獲取原始值信息
reflect.ValueOf()返回的是reflect.Value類型,其中包含了原始值信息,主要用於對象數據值的讀寫。
- 示例5-通過反射獲取對象值的信息
func main() {
a := 10
//a的類型爲int
fmt.Printf("%T %#v\n", a, a)
//獲取反射對象v v的類型爲reflect.Value
v := reflect.ValueOf(a)
fmt.Printf("%T %#v\n", v, v)
//轉換變量a的值以interfac{}返回
vi := v.Interface()
fmt.Printf("%T %#v\n", vi, v)
//轉換變量a的值以int類型返回
vii := v.Int()
fmt.Printf("%T %#v\n", vii, vii)
//獲取reflect value類型的變量種類
vk := v.Kind()
fmt.Printf("%T %#v\n", vk, vk)
//獲取變量v的類型
fmt.Println(v.Type())
//類型不符合無法轉換
//fmt.Println(v.Bytes())
//IsNil只用於引用類型
//fmt.Println(v.IsNil())
//判斷v是否有效
fmt.Println(v.IsValid())
//判斷v是否爲0
fmt.Println(v.IsZero())
}
/**
int 10
reflect.Value 10
int 10
int64 10
reflect.Kind 0x2
int
true
false
*/
由於接口變量會複製對象,而ValueOf的簽名是func ValueOf(i interface{}) Value。使用變量本身無法修改對象值因此需要修改指針。
reflect.ValueOf-通過反射修改對象值
- 示例6 通過反射來修改變量值
func main() {
//10 - 字面值常量地址不可達 無法獲取變量的地址
a := 10
va := reflect.ValueOf(a)
fmt.Println(va.CanAddr(), va.CanSet())
//無法賦值
//va.SetInt(11)
vp := reflect.ValueOf(&a)
fmt.Println(vp.CanAddr(), vp.CanSet())
//指針本身是地址不可達, 也無法設置值的。
//vp.SetInt(11)
//但是可以通過存放a的地址副本通過a的地址來關聯到a的內存地址,從而修改內存內容。
//修改a的值爲11
vp.Elem().SetInt(11)
fmt.Println(a)
fmt.Println(vp.Elem().CanAddr(), vp.Elem().CanSet())
}
/**
output:
false false
false false
11
true true
*/
- 由於接口interface{}會賦值對象,並且是地址不可達,因此需要修改對象值需要傳入對象指針。
- 傳入的對象指針通過Elem來定位到對象實際的內存空間,從而修改對象值。
- 使用CanAddr、CanSet來判斷是否可以尋址、可以設置值。
判斷對象是否爲空,或者有效
1. isNil
isNil的源碼可以看出只要是針對引用類型的變量判空處理。如果傳入非引用的變量就會panic。
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
func (v Value) IsNil() bool {
k := v.kind()
switch k {
case Chan, Func, Map, Ptr, UnsafePointer:
if v.flag&flagMethod != 0 {
return false
}
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
return ptr == nil
case Interface, Slice:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return *(*unsafe.Pointer)(v.ptr) == nil
}
panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}
- 示例7 使用IsNil判斷引用類型是否爲空
func main() {
var p *string
var s []int
//var i int
//IsNil主要用來判斷引用類型的變量的值是否爲nil
fmt.Println(reflect.ValueOf(p).IsNil())
fmt.Println(p == nil)
fmt.Println(reflect.ValueOf(s).IsNil())
fmt.Println(s == nil)
//panic: reflect: call of reflect.Value.IsNil on int Value
//fmt.Println(reflect.ValueOf(i).IsNil())
}
/**
output:
true
true
true
true
*/
2. isValid()
//源碼沒看明白丫
type flag uintptr
// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool {
return v.flag != 0
}
- TODO
結構體反射
通過reflect.TypeOf()獲得反射對象信息後,如果是struct類型,則可以通過反射對象獲取方法和成員域。
- 通過以下方法成員域
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// FieldByIndex returns the nested field corresponding
// to the index sequence. It is equivalent to calling Field
// successively for each index i.
// It panics if the type's Kind is not Struct.
FieldByIndex(index []int) StructField
// FieldByName returns the struct field with the given name
// and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)
- 通過以下方法獲取方法集的方法
// NumMethod returns the number of exported methods in the type's method set.
NumMethod() int
// Method returns the i'th method in the type's method set.
// It panics if i is not in the range [0, NumMethod()).
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
//
// Only exported methods are accessible and they are sorted in
// lexicographic order.
Method(int) Method
// MethodByName returns the method with that name in the type's
// method set and a boolean indicating if the method was found.
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
MethodByName(string) (Method, bool)
- 關鍵的兩個描述成員域和方法集的接口體
- StructField-描述成員域
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
}
- Method-描述方法的結構體
// Method represents a single method.
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
- 示例8 結構體獲取成員域和方法集
import (
"fmt"
"reflect"
)
type Animal struct {
name string
Age int
}
func (a *Animal) Name() string {
return a.name
}
func (a *Animal) SetName(name string) {
a.name = name
}
func (a Animal) GetAge() int {
return a.Age
}
func main() {
animal := Animal{
name: "animal",
Age: 0,
}
ty := reflect.TypeOf(animal)
fmt.Println("==========================Field========================")
//獲取結構體成員域個數
fmt.Println(ty.NumField())
//獲取成員域的個數
for i := 0; i < ty.NumField(); i++ {
//通過索引去遍歷成員域
f := ty.Field(i)
fmt.Println(f.Name, f.Index, f.Type, f.Tag, f.Offset)
}
f, _ := ty.FieldByName("name")
//通過FieldByName去嘗試獲取字段順序
fmt.Println("FieldByName->name", f.Name, f.Index, f.Type, f.Tag, f.Offset)
f, _ = ty.FieldByName("Name")
//找不到名字爲Name的域
fmt.Println("FieldByName->name", f.Name, f.Index, f.Type, f.Tag, f.Offset)
fmt.Println("==========================Method========================")
//獲取方法集方法個數
fmt.Println(ty.NumMethod())
for i := 0; i < ty.NumMethod(); i++ {
//通過索引去獲取方法集中的方法
m := ty.Method(i)
fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
}
//通過方法名嘗試獲取方法值
m, _ := ty.MethodByName("GetAge")
fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
//通過方法名嘗試獲取方法值
m, _ = ty.MethodByName("Name")
fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
}
/**
output:
==========================Field========================
2
name [0] string 0
Age [1] int 16
FieldByName->name name [0] string 0
FieldByName->name [] <nil> 0
==========================Method========================
1
func(main.Animal) int 0 GetAge 0x4cc0f0
func(main.Animal) int 0 GetAge 0x4cc0f0
<nil> 0 <invalid reflect.Value>
*/
- 反射到方法然後進行實例方法的調用
- 示例9 通過結構體變量進行方法調用
type Animal struct {
name string
Age int
}
func (a *Animal) Name() string {
fmt.Println("getName:", a.name)
return a.name
}
func (a *Animal) SetName(name string) {
a.name = name
fmt.Println("setName")
}
func main() {
//var in []reflect.Value
animal := Animal{
name: "",
Age: 0,
}
//獲取方法名
ty := reflect.TypeOf(&animal)
for i := 0; i < ty.NumMethod(); i++ {
meth := ty.Method(i)
fmt.Printf("TYPE index [%d] = [%s]\n", i, meth.Name)
}
va := reflect.ValueOf(&animal)
for i := 0; i < va.NumMethod(); i++ {
//meth := va.Method(i)
fmt.Printf("TYPE index [%d] = [%s]\n", i, ty.Method(i).Name)
}
var in []reflect.Value
//構造Name入參
va.Method(0).Call(in)
//構造setName--入參
var in1 = []reflect.Value{reflect.ValueOf("rewr")}
//通過實例值的方法進行方法調用,並修改對應的值
va.Method(1).Call(in1)
va.Method(0).Call(in)
}
/**
output:
TYPE index [0] = [Name]
TYPE index [1] = [SetName]
TYPE index [0] = [Name]
TYPE index [1] = [SetName]
getName:
setName
getName: rewr
*/
可以對結構體信息Valueof來獲取方法集合,然後通過對應的索引值來獲取方法對象,然後對方法對象進行.
Call
進行調用方法。
方法的參數是[]reflect.Value–切片。空參數送空切片。一個參數就是獲取切片下標爲0的元素。依次類推。。
需要注意的是調用方法一定是通過ValueOf而不是TypeOf
5. 反射操作tag
- 示例10 通過結構體變量進行方法調用
import (
"fmt"
"reflect"
)
type Animal struct {
name string `json:"Name" xml:"_name"`
age int `json:"Age" xml:"_age"`
}
func (a *Animal) Age() int {
return a.age
}
func (a *Animal) SetAge(age int) {
a.age = age
}
func (a *Animal) Name() string {
return a.name
}
func (a *Animal) SetName(name string) {
a.name = name
}
func NewAnimal(name string, age int) *Animal {
return &Animal{name: name, age: age}
}
func getTagFromStruct(s interface{}) {
ty := reflect.TypeOf(s)
for i := 0; i < ty.NumField(); i++ {
field := ty.Field(i)
//通過Tag標籤的Get方法來獲取tag-標籤值 實際上就是一個字符串的處理
fmt.Printf("name: %s|tag:[%s] subtag:xml->[%s] json->[%s] othr->[%s]\n",
field.Name, field.Tag, field.Tag.Get("xml"), field.Tag.Get("json"), field.Tag.Get("othr"))
}
}
func main() {
a := NewAnimal("lisi", 10)
getTagFromStruct(*a)
}
/**
output:
name: name|tag:[json:"Name" xml:"_name"] subtag:xml->[_name] json->[Name] othr->[]
name: age|tag:[json:"Age" xml:"_age"] subtag:xml->[_age] json->[Age] othr->[]
*/
實際上獲取標籤是比較容易的,通過索引獲取的
StructField
對象,然後通過對象StructField
的成員域獲取Tag StructTag // field tag string
就能拿到描述結構體的元數據,在通過Get方法對字符串json:"Name" xml:"_name"
遍歷解析即可。