天天看點

golang反射簡介反射interface

文章目錄

  • 反射
    • 反射類型Type
      • 指針
      • 結構體
    • 反射值Value
      • 結構體
      • 空與有效性判斷
      • 修改值
      • 函數調用
    • 反射三定律
  • interface
    • 底層結構
      • iface
      • eface

反射是一種讓程式可以在運作時( runtime )檢查其資料結構的能力,通過反射可以擷取豐富的類型資訊。

反射

Go語言提供了reflect 包來通路程式的反射資訊;定義了兩個重要的類型Type和Value:

  • reflect.TypeOf:擷取任意值的類型對象(reflect.Type);
  • reflect.ValueOf:獲得值的反射值對象(reflect.Value);

反射類型Type

Go語言程式中的類型(Type)指的是系統原生資料類型(如 int、string、bool、float32 等),以及使用 type 關鍵字定義的類型;而反射種類(Kind)是指對象的歸屬分類:

type Kind uint

const (
    Invalid Kind = iota  // 非法類型
    Bool                 // 布爾型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 無符号整型
    Uint8                // 無符号8位整型
    Uint16               // 無符号16位整型
    Uint32               // 無符号32位整型
    Uint64               // 無符号64位整型
    Uintptr              // 指針
    Float32              // 單精度浮點數
    Float64              // 雙精度浮點數
    Complex64            // 64位複數類型
    Complex128           // 128位複數類型
    Array                // 數組
    Chan                 // 通道
    Func                 // 函數
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指針
    Slice                // 切片
    String               // 字元串
    Struct               // 結構體
    UnsafePointer        // 底層指針
)
           

指針

對指針指向的對象,可通過reflect.Elem() 方法擷取這個指針指向的元素類型(等效于對指針類型變量做了一個

*

操作)。

func reflectStruct() {
	type Cat struct {
	}

	aCat := &Cat{}
	// 擷取結構體執行個體的反射類型對象
	typeOfCat := reflect.TypeOf(aCat)
	fmt.Printf("ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())

	// 取類型的元素
	typeOfCat = typeOfCat.Elem()
	fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())

	typeOfCat = reflect.TypeOf(*aCat)
	fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}

// ptr name:'' kind:'ptr'
// element name: 'Cat', element kind: 'struct'
// *ptr name:'Cat' kind:'struct'
           

結構體

對結構體對象,擷取對象資訊後,可通過NumField() 和 Field() 方法獲得結構體成員的詳細資訊。

方法 說明
Field(i int) StructField 根據索引傳回索引對應字段的資訊
NumField() int 傳回結構體成員字段數量
FieldByName(name string) (StructField, bool) 根據給定字元串傳回字元串對應的結構體字段的資訊,沒有找到時 bool 傳回 false
FieldByIndex(index []int) StructField 多層成員通路時,根據 []int 提供的每個結構體的字段索引,傳回字段的資訊,沒有找到時傳回零值
FieldByNameFunc(match func(string) bool) (StructField,bool) 根據比對函數比對需要的字段

字段資訊中含有:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段在結構體中的路徑
    Type      Type       // 字段的反射類型 reflect.Type
    Tag       StructTag  // 字段的結構體标簽
    Offset    uintptr    // 字段在結構體中的相對偏移
    Index     []int      // FieldByIndex中的索引順序
    Anonymous bool       // 是否為匿名字段
}
           

擷取字段的名稱與tag:

func reflectStructField()  {
	// 聲明一個空結構體
	type Cat struct {
		Name string
		// 帶有結構體tag的字段
		Type int `json:"type" id:"100"`
	}

	aCat := Cat{Name: "mimi", Type: 1}
	// 擷取結構體執行個體的反射類型對象
	typeOfCat := reflect.TypeOf(aCat)
	// 周遊結構體所有成員
	for i := 0; i < typeOfCat.NumField(); i++ {
		fieldType := typeOfCat.Field(i)
		fmt.Printf("Field-%v: name: %v  tag: '%v'\n", i, fieldType.Name, fieldType.Tag)
	}

	// 通過字段名, 找到字段類型資訊
	if catType, ok := typeOfCat.FieldByName("Type"); ok {
		fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id"))
	}
}

// Field-0: name: Name  tag: ''
// Field-1: name: Type  tag: 'json:"type" id:"100"'
// Field Tag:  type 100
           

反射值Value

通過下面幾種方法從反射值對象 reflect.Value 中擷取原值

方法名 說 明
Interface() interface {} 将值以 interface{} 類型傳回,可以通過類型斷言轉換為指定類型
Int() int64 将值以 int 類型傳回,所有有符号整型均可以此方式傳回
Uint() uint64 将值以 uint 類型傳回,所有無符号整型均可以此方式傳回
Float() float64 将值以雙精度(float64)類型傳回,所有浮點數(float32、float64)均可以此方式傳回
Bool() bool 将值以 bool 類型傳回
Bytes() []bytes 将值以位元組數組 []bytes 類型傳回
String() string 将值以字元串類型傳回

通過反射擷取變量的值:

func reflectValue()  {
	var a int = 1024

	// 擷取變量a的反射值對象
	valueOfA := reflect.ValueOf(a)

	// 擷取interface{}類型的值, 通過類型斷言轉換
	var getA int = valueOfA.Interface().(int)

	// 擷取64位的值, 強制類型轉換為int類型
	var getA2 int = int(valueOfA.Int())
	
	fmt.Println(getA, getA2)
}

// 1024 1024
           

結構體

反射值對象(reflect.Value)提供對結構體通路的方法,通過這些方法可以完成對結構體任意值的通路:

方 法 備 注
Field(i int) Value 根據索引,傳回索引對應的結構體成員字段的反射值對象
NumField() int 傳回結構體成員字段數量
FieldByName(name string) Value 根據給定字元串傳回字元串對應的結構體字段,沒有找到時傳回零值
FieldByIndex(index []int) Value 多層成員通路時,根據 []int 提供的每個結構體的字段索引,傳回字段的值; 沒有找到時傳回零值
FieldByNameFunc(match func(string) bool) Value 根據比對函數比對需要的字段,沒有找到時傳回零值

空與有效性判斷

反射值對象(reflect.Value)提供一系列方法進行零值和空判定:

方 法 說 明
IsNil() bool 是否為 nil,隻對通道、函數、接口、map、指針或切片有效(否則會panic)
IsValid() bool 是否有效,當值本身非法時(不包含任何值,或值為 nil),傳回 false

修改值

通過反射修改變量值的前提條件之一:這個值必須可以被尋址,簡單地說就是這個變量必須能被修改。結構體成員中,如果字段沒有被導出,即便也可以被通路,也不能通過反射修改。

方法名 備 注
Elem() Value 取值指向的元素值(類似于

*

操作);對指針或接口時發生panic
Addr() Value 對可尋址的值傳回其位址(類似于

&

操作);當值不可尋址時發生panic
CanAddr() bool 表示值是否可尋址
CanSet() bool 傳回值能否被修改;要求值可尋址且是導出的字段

修改結構體字段的值(需要結構體位址,與導出字段):

func reflectModifyValue()  {
	type Dog struct {
		LegCount int
	}
	// 擷取dog執行個體位址的反射值對象
	valueOfDog := reflect.ValueOf(&Dog{})
	// 取出dog執行個體位址的元素
	valueOfDog = valueOfDog.Elem()
	
	vLegCount := valueOfDog.FieldByName("LegCount")
	vLegCount.SetInt(4)
	fmt.Println(vLegCount.Int())
}
           

函數調用

如果反射值對象(reflect.Value)為函數,可以通過Call()調用:參數使用反射值對象的切片[]reflect.Value構造後傳入,傳回值通過[]reflect.Value傳回。

func add(a, b int) int {
	return a + b
}
func reflectFunction() {
	// 将函數包裝為反射值對象
	funcValue := reflect.ValueOf(add)
	// 構造函數參數, 傳入兩個整型值
	paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
	// 反射調用函數
	retList := funcValue.Call(paramList)
	// 擷取第一個傳回值, 取整數值
	fmt.Println(retList[0].Int())
}
           

反射三定律

官方提供了三條定律來說明反射:

  • 反射可将interface類型變量轉換成反射對象;
  • 反射可将反射對象還原成interface對象;
  • 要修改反射對象,其值必須是可寫的(反射其指針類型);
var x float64 = 3.4

v := reflect.ValueOf(x) // v is reflext.Value
var y float64 = v.Interface().(float64)

fmt.Println("value:", y) // 3.4
           

值類型不能直接修改,可通過傳遞位址并通過Elem擷取後修改:

var x float64 = 3.4

v := reflect.ValueOf(&x)
v.Elem().SetFloat(7.8)

fmt.Println("x :", v.Elem().Interface())	// 7.8
           

interface

interface是Go實作抽象的一個非常強大的工具;當向接口指派時,接口會存儲實體的類型資訊;反射就是通過接口的類型資訊實作的。

interface類型是一種特殊類型,代表方法集合;可存放任何實作了其方法的值(實際存放的是

(value,type)

對)。reflect包中實作了反射的各種函數:

  • 提取interface的value的方法

    reflect.ValueOf()->reflect.Value

  • 提取interface的type的方法

    reflect.TypeOf()->reflect.Type

空interface類型(

interface{}

)的方法集為空,是以可認為任何類型都實作了該接口;是以其可存放任何值。

底層結構

interface底層結構分為iface和eface描述接口,其差別是eface為不包含任何方法的空接口。

iface

iface定義如下:

  • tab

    指向一個

    itab

    實體的指針:表示接口的類型(賦給此接口的實體類型);
  • data

    指向接口具體的值:一般而言是一個指向堆記憶體的指針。
type iface struct {
	tab  *itab
	data unsafe.Pointer
}
           

itab結構:

  • _type

    字段描述了實體的類型:包括記憶體對齊方式,大小等;
  • inter

    字段則描述了接口的類型;
  • fun

    字段放置是實體類中和接口方法對應(實體中其他方法不在此處)的方法位址,以實作接口調用方法的動态分派;一般在每次給接口指派發生轉換時會更新此表。
type itab struct {
	inter  *interfacetype
	_type  *_type
	link   *itab
	hash   uint32
	bad    bool
	inhash bool
	unused [2]byte
	fun    [1]uintptr
}
           

iface結構全貌圖:

golang反射簡介反射interface

eface

eface結構:隻維護了一個

_type

字段,表示空接口所承載的具體的實體類型。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
           

繼續閱讀