Go中的反射reflect

前面我們在學習到struct結構體的時候,因爲結構體中的字段首字母大寫,而我們想把json文件映射到該結構體上時,需要在在結構體字段後面加上json標籤,表明結構體字段和json字段的映射關係。這其中就用到了反射的方式去獲取標籤,取出該標籤對應的json字段然後存儲到結構體字段上。

Go語言中提供了反射的包爲reflect。在 reflect 包中,主要通過兩個函數TypeOf()ValueOf()實現反射,TypeOf()獲取到的結果是reflect.Type 類型,ValueOf()獲取到的結果是reflect.Value類型。

1. 理解反射的類型(Type)

reflect.TypeOf()返回的是Type類型,Type中包含了一個對象會有的相關信息,對象名,對象類型,對象的方法,對象中的屬性等等。

reflect.Type中的方法:

// 通用方法

func (t *rtype) String() string // 獲取 t 類型的字符串描述,不要通過 String 來判斷兩種類型是否一致。

func (t *rtype) Name() string // 獲取 t 類型在其包中定義的名稱,未命名類型則返回空字符串。

func (t *rtype) PkgPath() string // 獲取 t 類型所在包的名稱,未命名類型則返回空字符串。

func (t *rtype) Kind() reflect.Kind // 獲取 t 類型的類別。

func (t *rtype) Size() uintptr // 獲取 t 類型的值在分配內存時的大小,功能和 unsafe.SizeOf 一樣。

func (t *rtype) Align() int  // 獲取 t 類型的值在分配內存時的字節對齊值。

func (t *rtype) FieldAlign() int  // 獲取 t 類型的值作爲結構體字段時的字節對齊值。

func (t *rtype) NumMethod() int  // 獲取 t 類型的方法數量。

func (t *rtype) NumField() int //返回一個struct 類型 的屬性個數,如果非struct類型會拋異常

func (t *rtype) Method() reflect.Method  // 根據索引獲取 t 類型的方法,如果方法不存在,則 panic。
// 如果 t 是一個實際的類型,則返回值的 Type 和 Func 字段會列出接收者。
// 如果 t 只是一個接口,則返回值的 Type 不列出接收者,Func 爲空值。

func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根據名稱獲取 t 類型的方法。

func (t *rtype) Implements(u reflect.Type) bool // 判斷 t 類型是否實現了 u 接口。

func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判斷 t 類型的值可否轉換爲 u 類型。

func (t *rtype) AssignableTo(u reflect.Type) bool // 判斷 t 類型的值可否賦值給 u 類型。

func (t *rtype) Comparable() bool // 判斷 t 類型的值可否進行比較操作
//注意對於:數組、切片、映射、通道、指針、接口 
func (t *rtype) Elem() reflect.Type // 獲取元素類型、獲取指針所指對象類型,獲取接口的動態類型

有個方法是Elem(),獲取元素類型、獲取指針所指對象類型,獲取接口的動態類型。對指針類型進行反射的時候,可以通過reflect.Elem()獲取這個指針指向元素的類型。

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type User struct {
	Name string
	Age  int
}

func (User) GetUser(user User) string{
	return user.Name + " " + strconv.Itoa(user.Age)
}

func main() {
	user := &User{"xiaoming", 13}
	of := reflect.TypeOf(user)
	elem := reflect.TypeOf(user).Elem()
	fmt.Println(of.Kind(),of.Name())
	fmt.Println(elem.Kind(),elem.Name())
}

結果:
ptr 
struct User

從上面的結果來看,TypeOf(user)的種類爲ptr,Go中反射對所有的指針變量的種類都是ptr,但是指針變量的類型名稱是空的。

通過Elem()方法可以得到指針指向的元素的類型和名稱,得到User的類型爲struct,名稱爲User。

1.1 通過反射獲取結構體成員類型
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type User struct {
	Name string
	Age  int
}

func (User) GetUser(user User) string{
	return user.Name + " " + strconv.Itoa(user.Age)
}


func main() {
	user := User{"xiaoming", 13}

	//反射獲取對象類型,字段類型
	userType := reflect.TypeOf(user)
	fmt.Println(userType.Name(),userType.Kind())

	for i := 0; i < userType.NumField(); i++ {
		fieldType := userType.Field(i)
		fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)
	}
	
	if name, ok := userType.FieldByName("Name"); ok {
		fmt.Println(name)
	}
}


輸出信息:
User struct
name: Name  tag: ''
name: Age  tag: ''
{Name  string  0 [0] false}

在上面的代碼中,NumField()方法獲取結構體類型中的屬性個數,通過Field(i)方法來獲取結構體中的屬性,返回的是StructField類型的結構體。該對象中包含如下信息:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路徑
    Type      Type       // 字段反射類型對象
    Tag       StructTag  // 字段的結構體標籤
    Offset    uintptr    // 字段在結構體中的相對偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否爲匿名字段
}

可以看到有我們關注的字段名,字段類型,還有tag類型等等。

2. 通過反射獲取對象值

獲取對象值通過ValueOf()方法來實現。

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type User struct {
	Name string
	Age  int
}

func (User) GetUser(user User) string{
	return user.Name + " " + strconv.Itoa(user.Age)
}

func main() {
	user := User{"xiaoming", 13}
	
	//反射獲取對象值
	elem := reflect.ValueOf(user)
	fmt.Println(elem)
	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		i2 := field.Type()
		fmt.Println(i2.Name(), " ", field)
	}
}

輸出:
{xiaoming 13}
string   xiaoming
int   13

2.1 創建對象和調用方法
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type User struct {
	Name string
	Age  int
}

func (User) GetUser(user User) string{
	return user.Name + " " + strconv.Itoa(user.Age)
}

func main() {
	user1 := User{"xiaoming", 13}
	user := User{}


	//反射獲取對象值
	elem := reflect.TypeOf(user)
	//創建一個實例
	value:= reflect.New(elem).Elem()
	value.Field(0).SetString("xiaohong")
	value.Field(1).SetInt(15)

	fmt.Println(value.Field(0),value.Field(1))

	of := reflect.ValueOf(user)
	params := make([]reflect.Value,1)
	params[0] = reflect.ValueOf(user1)
	//調用方法,傳遞參數
	call := of.Method(0).Call(params)
	fmt.Println(call)
}

輸出:

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