Go語言入門-反射

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.TypeOfreflect.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來判斷。

  1. 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
 */
  1. 由於接口interface{}會賦值對象,並且是地址不可達,因此需要修改對象值需要傳入對象指針。
  2. 傳入的對象指針通過Elem來定位到對象實際的內存空間,從而修改對象值。
  3. 使用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類型,則可以通過反射對象獲取方法和成員域。

  1. 通過以下方法成員域
	// 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)
  1. 通過以下方法獲取方法集的方法

	// 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)
  1. 關鍵的兩個描述成員域和方法集的接口體
  • 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>

*/
  1. 反射到方法然後進行實例方法的調用
  • 示例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" 遍歷解析即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章