go反射詳解

 

目錄

前言

思考題

反射的三大法則及go中對應的實現

go反射概述

Type詳解

Method

Kind

StructField

Value詳解

比較通用的函數 

函數

數組、切片、字符串

通道

映射

結構體

從反射類型得到接口數據

Setter函數

Overflow一類的函數

其他函數

go反射最佳實踐

從reflect.ValueOf開始

如果想修改數據

Elem()的替代函數

最初的思考

參考文章


前言

書上對go反射的講解太淺顯,網上的博客也太零散,想着自己整理一篇博客吧,爲解決當前的一個問題,也爲以後複習參考。

思考題

在讀這篇博客之前可以先想象這樣一個情景:自己設計一個類似 json.Marshal(v interface{})的函數,按順序遞歸打印出入參的每一個字段的名稱和值。

這也是我當前遇到的需求的一個環節。

反射的三大法則及go中對應的實現

  1. 能根據接口數據獲得反射對象。對應go中reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})兩個方法。
  2. 能根據反射對象獲得接口數據。對應go中Value.Interface()、Value.[基本數據類型]()等方法。
  3. 如果數據可修改,則能通過反射對象修改。對應go中Value.CanSet()和Value的Set系列方法。

go反射概述

  1. go不支持解析string然後執行。golang的反射機制只能存在於已經存在的對象上面。
  2. go的反射主要由Type和Value組成。可分別由reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})獲得。Type是一個接口,有若干函數,reflect.TypeOf(i interface{})返回的是實現了這個接口的具體實現。Value是一個結構體,有若干字段和函數。
  3. Type也可由Value.Type()獲得,Value不可由Type獲得。
  4. Type存儲接口的類型信息,例如方法數量、方法名、字段數量、字段名等。當使用Type時應當只讀取信息,不能試圖通過Type進行修改、調用之類的操作。
  5. Value存儲接口的數據信息,並且包含對應的Type,可以通過Value.Type()獲得對應的Type。

Type詳解

通過go中Type的定義,可以看出Type及其具體實現有哪些方法可以用。下面給幾個常用方法做了註釋

type Type interface {
	Name() string    //返回類型名
	Kind() Kind        //返回該類型的種類,Array、Slice、Struct等
	Elem() Type    //當type當kind是Array, Chan, Map, Ptr, 或 Slice使用,返回類型的元素類型。
	Method(int) Method    // 按下標返回Method,超出範圍Panic
	MethodByName(string) (Method, bool)   // 按方法名稱返回Method和表示方法是否存在的bool
	NumMethod() int    // 返回方法數量

	NumIn() int       //當kind是Func時,返回入參數量
	In(i int) Type    //當Kind是Func時,返回第i個入參類型
	NumOut() int      //當kind是Func時,返回出參數量
	Out(i int) Type   //當kind是Func時,返回第i個出參類型

	NumField() int    //當Kind是Struct時,返回字段數量,內嵌結構體算一個。
	Field(i int) StructField    //當Kind是Struct時,通過下標返回字段
	FieldByIndex(index []int) StructField    //當Kind是Struct時,返回內嵌結構體字段
	FieldByName(name string) (StructField, bool) //當Kind是Struct時,通過名稱返回字段
	FieldByNameFunc(match func(string) bool) (StructField, bool) //當Kind是Struct時,按自定義字段名匹配函數返回字段
	
        Key() Type        //當Kind時map時,返回鍵的類型,值的類型可由Elem()返回

	Len() int         //當類型是Array時,返回長度


	PkgPath() string //返回定義類型的包的路徑
	Size() uintptr    //返回存儲一個該類型的值所需要的字節數
	Align() int
	FieldAlign() int
	String() string
	Implements(u Type) bool
	AssignableTo(u Type) bool
	ConvertibleTo(u Type) bool
	Comparable() bool
	Bits() int
	ChanDir() ChanDir
	IsVariadic() bool
	common() *rtype
	uncommon() *uncommonType
}

Method

Type中Method(int) Method、MethodByName(string) (Method, bool)、NumMethod() int三個方法與Method有關,Method是一個結構體,存儲了方法名、方法在Type方法集中的下標等信息。

type Method struct {
	Name    string //方法名
	PkgPath string

	Type  Type
	Func  Value
	Index int // 在Type方法集中的下標
}

示例代碼:

func (user User) GetId() int {
	log.Println("[User] GetId begin")
	log.Printf("[User] age:%d", user.age)
	log.Println("[User] GetId end")
	return user.age
}

func main() {
	user := User{
		Name: "張三",
		age:  18,
	}
	userType := reflect.TypeOf(user)
	log.Printf("userType.NumMethod():%d", userType.NumMethod()) //1
	method, exist := userType.MethodByName("GetId")
	if exist {
		log.Printf("method.Name:%+v", method.Name)       //GetId
		log.Printf("method.PkgPath:%+v", method.PkgPath) //
		log.Printf("method.Type:%+v", method.Type)       //func(main.User) int
		log.Printf("method.Func:%+v", method.Func)       //0x10bd9f0
		log.Printf("method.Index:%+v", method.Index)     //0
	}
	for i := 0; i < userType.NumMethod(); i++ {
		method := userType.Method(i)
		log.Printf("method.Name:%+v", method.Name)       //GetId
		log.Printf("method.PkgPath:%+v", method.PkgPath) //
		log.Printf("method.Type:%+v", method.Type)       //func(main.User) int
		log.Printf("method.Func:%+v", method.Func)       //0x10bd9f0
		log.Printf("method.Index:%+v", method.Index)     //i
	}

}

 

 

Kind

Type的Kind()方法返回該類型的種類,可根據不同的種類進行不同的操作。reflect中枚舉的種類有以下幾種:

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
)

 

StructField

先通過一個例子說明一下FieldByIndex(index []int) StructField函數

type Point struct {
	X int
	Y int
	Z int
}
type User struct {
	Name string
	Age  int
	Point
}

func main() {
	user := User{
		Name: "張三",
		Age:  18,
	}
	userType := reflect.TypeOf(user)
	index := []int{2, 1} //代表下標爲2的字段是一個內嵌結構體,取出這個內嵌結構體中下標爲1的字段
	field := userType.FieldByIndex(index)
	log.Printf("userType.FieldByIndex():%+v", field)
}

StructField記錄結構體的字段信息,包括字段名稱、字段標籤、字段是否爲內嵌結構體、字段在結構體中的偏移量等。

type StructField struct {
	Name string         //名稱
	PkgPath string
	Type      Type      //類型
	Tag       StructTag //標籤
	Offset    uintptr   //如果是內嵌結構體裏的字段,則從內嵌結構體裏面重新計算偏移量
	Index     []int     //如果是內嵌結構體裏的字段,則從內嵌結構體裏面重新計數下標
	Anonymous bool      //是否是內嵌結構體
}

Value詳解

先看一下Value的定義:

type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag
}

可以看到Value只有三個非導出字段,這表面我們對Value的一切操作都要通過調用函數。值得注意的是typ *rtype字段,這是Value對應的Type實例。我們可以通過Value.Type()獲得這個實例。

Value有61個可用函數。下面分類看看這些函數

比較通用的函數 

func (v Value) CanAddr() bool //判斷v是否可尋址。如果v是切片元素、可尋址數組元素、可尋址結構體字段、或ptr執行Elem()後返回的結果,那麼v是可尋址的。
func (v Value) CanSet() bool //判斷v是否可修改。可當v尋址且是導出字段時可修改。
func (v Value) Elem() Value    //當v的kind是Interface 或 Ptr,返回v指向的實例。
func (v Value) Addr() Value    //當v可尋址是返回v當指針Value,和Elem()互爲逆操作。
func (v Value) Kind() Kind    //返回v的Kind。
func (v Value) IsNil() bool   //當v的kind是Chan, Func, Map, Ptr, UnsafePointer時,判斷v是否是nil
func (v Value) IsValid() bool //判斷v是否有效
func (v Value) IsZero() bool  //判斷v是否是它所屬類型的零值
func (v Value) Pointer() uintptr //當v的 Kind is not Chan, Func, Map, Ptr, Slice, or UnsafePointer時使用。
func (v Value) UnsafeAddr() uintptr //返回指向v的數據的指針
 

函數

func (v Value) NumMethod() int    //返回v的函數數量。
func (v Value) Method(i int) Value     //按下標返回函數。
func (v Value) MethodByName(name string) Value   //按名字返回函數。


func (v Value) Call(in []Value) []Value //當v的Kind是Func時,執行函數v。in是入參,返回是出參。
func (v Value) CallSlice(in []Value) []Value //當vKind是Func且是變參函數時,執行函數。in是入參,返回是出參。

數組、切片、字符串

func (v Value) Cap() int //當v的Kind是Array、Slice、Chan時返回v的容量。
func (v Value) Len() int //當v的Kind是Array、Slice、String、Chan、Map時返回v的長度。
func (v Value) Index(i int) Value    //當v的Kind是Array、Slice、String時按下標返回元素。
func (v Value) Slice(i, j int) Value    //當v的Kind是Array、Slice、String時,返回v[i:j]
func (v Value) Slice3(i, j, k int) Value    //當v的Kind是Array、Slice時,返回v[i:j:k]

通道

func (v Value) Len() int
func (v Value) Cap() int
func (v Value) Recv() (x Value, ok bool) 
func (v Value) Send(x Value)
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Close()    //當v的kind是Chan時,關閉v。

映射

func (v Value) Len() int
func (v Value) MapKeys() []Value    //當v的Kind是Map時,返回鍵數組
func (v Value) MapIndex(key Value) Value    //當v的Kind是Map時,輸入鍵返回值。
func (v Value) MapRange() *MapIter //當v的Kind是Map時,返回MapIter實例。

 MapIter是遍歷Map的迭代器,它的字段都是非導出字段,我們只需要關心MapIter的方法:

func (it *MapIter) Key() Value
func (it *MapIter) Value() Value
func (it *MapIter) Next() bool

結構體

func (v Value) Field(i int) Value    //當v的kind是Struct時,按下標返回字段
func (v Value) FieldByIndex(index []int) Value    //當v的kind是Struct時,返回內嵌字段
func (v Value) FieldByName(name string) Value    //當v的kind是Struct時,按名字返回字段
func (v Value) FieldByNameFunc(match func(string) bool) Value    //當v的kind是Struct時,按自定義字段名匹配規則返回字段。

從反射類型得到接口數據

從反射類型轉化接口數據的方法,對應反射的第二法則。結構體可先轉化爲接口再進行斷言。基本數據類型和[]byte、[]rune有一步到位的方法。

func (v Value) Interface() (i interface{})

func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) String()
func (v Value) Float() float64
func (v Value) Complex() complex128

func (v Value) Bytes() []byte
func (v Value) runes() []rune

Setter函數

func (v Value) Set(x Value) 
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte) 
func (v Value) setRunes(x []rune) 
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetCap(n int)
func (v Value) SetMapIndex(key, elem Value) 
func (v Value) SetUint(x uint64)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)

Overflow一類的函數

func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool

其他函數


// CanInterface reports whether Interface can be used without panicking.
func (v Value) CanInterface() bool
// InterfaceData returns the interface v's value as a uintptr pair.
// It panics if v's Kind is not Interface.
func (v Value) InterfaceData() [2]uintptr
// Convert returns the value v converted to type t.
// If the usual Go conversion rules do not allow conversion
// of the value v to type t, Convert panics.
func (v Value) Convert(t Type) Value

 

go反射最佳實踐

從reflect.ValueOf開始

從上文中可以看出,Type能做的事Value基本都能做,Value不能做的事可以通過Value.Type()獲得對應的Type再做,所以一般反射可以從reflect.ValueOf開始。如果只需要讀取結構信息,可以從reflect.TypeOf開始。

如果想修改數據

在go語言中可以被修改的數據必須滿足以下兩個條件:

  1. 可以被尋址
  2. 被導出

第一個條件很好理解,畢竟要修改數據的值,就得找到數據所在的地址。那什麼樣的變量才叫可尋址呢?分兩步走。

第一步:reflect.ValueOf(i interface{})函數要傳入指針類型變量。

第二步:對reflect.ValueOf獲得的對象執行Elem()函數。Elem()函數返回的Value就是可尋址的。示例代碼:

func main() {
	i := 1
	v1 := reflect.ValueOf(&i)
	v2 := v1.Elem()

	//可用Value.CanAddr()判斷是不是可以被尋址。
	fmt.Println(v1.CanAddr()) // false
	fmt.Println(v2.CanAddr()) // true
}

所以如果想通過反射修改數據,就向reflect.ValueOf傳入一個指針。

如果只想通過反射讀取一些信息,爲了避免誤改,就向reflect.ValueOf傳入一份拷貝。當傳入非指針實例時go會自動拷貝數據。

Elem()的替代函數

func (v Value) Elem() Value函數不是任何Value都能調用的,如果v的 Kind 不是 Interface 或 Ptr就會導致Panic。我們遇到的大多數需要執行Elem()的時候都是v的 Kind是Ptr的時候。爲了避免每次都手動判斷v的Kind是不是Ptr,可以藉助reflect包裏的func Indirect(v Value) Value函數。這個函數會自行判斷v的是不是Kind是不是Ptr,如果是則返回v.Elem(),否則返回v本身。

最初的思考

現在再看文章剛開始的思考題,似乎已經沒那麼難了。下面貼出來代碼,有興趣的可以看看。

package main

import (
	"log"
	"reflect"
)

type People struct {
	Name string
	Age  int
}
type User struct {
	Id int64
	People
	friends []*People
	ext     map[string]interface{}
}

func main() {

	user := User{
		Id: 1,
		People: People{
			Name: "張三",
			Age:  18,
		},
		friends: []*People{
			{
				Name: "李四",
				Age:  18,
			},
			{
				Name: "朱五",
				Age:  18,
			},
		},
		ext: map[string]interface{}{
			"teacher": People{
				Name: "楊六",
				Age:  36,
			},
		},
	}
	Handle(user)
}
func Handle(i interface{}) {
	val := reflect.ValueOf(i)
	route(val)
}

func route(val reflect.Value) {
	if !val.IsValid() {
		return
	}
	switch val.Kind() {
	case reflect.Struct:
		handleStruct(val)
	case reflect.Slice:
		handleSlice(val)
	case reflect.Map:
		handleMap(val)
	case reflect.Ptr, reflect.Interface:
		route(val.Elem())
	default:
		log.Printf("處理能力之外的Kind,Kind:%+v", val.Kind())
	}
}
func handleStruct(obj reflect.Value) {
	objType := obj.Type()
	for i := 0; i < obj.NumField(); i++ {
		field := obj.Field(i)

		switch field.Kind() {
		case reflect.Array, reflect.Slice, reflect.Chan, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Struct:
			route(field)
		default:
			log.Printf("%s:%v", objType.Field(i).Name, field)
		}

	}
}
func handleSlice(list reflect.Value) {
	for i := 0; i < list.Len(); i++ {
		element := list.Index(i)
		route(element)
	}
}
func handleMap(m reflect.Value) {
	for iter := m.MapRange(); iter.Next(); {
		route(iter.Value())
	}
}

參考文章

go源碼

https://www.jb51.net/article/180540.htm

http://c.biancheng.net/view/116.html

 

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