Go利用反射reflect實現獲取接口變量信息

引言

反射是通過實體對象獲取反射對象(Value、Type),然後可以操作相應的方法。在某些情況下,我們可能並不知道變量的具體類型,這時候就可以用反射來獲取這個變量的類型或者方法。

一、反射的規則

其實反射的操作步驟非常的簡單,就是通過實體對象獲取反射對象(Value、Type),然後操作相應的方法即可。

下圖描述瞭實例、Value、Type 三者之間的轉換關系:

反射 API 的分類總結如下:

1、從實例到 Value

通過實例獲取 Value 對象,直接使用 reflect.ValueOf() 函數。例如:

func ValueOf(i interface {}) Value

2、從實例到 Type

通過實例獲取反射對象的 Type,直接使用 reflect.TypeOf() 函數。例如:

func TypeOf(i interface{}) Type

3、從 Type 到 Value

Type 裡面隻有類型信息,所以直接從一個 Type 接口變量裡面是無法獲得實例的 Value 的,但可以通過該 Type 構建一個新實例的 Value。reflect 包提供瞭兩種方法,示例如下:

//New 返回的是一個 Value,該 Value 的 type 為 PtrTo(typ),即 Value 的 Type 是指定 typ 的指針類型
func New(typ Type) Value
//Zero 返回的是一個 typ 類型的零佳,註意返回的 Value 不能尋址,位不可改變
func Zero(typ Type) Value

如果知道一個類型值的底層存放地址,則還有一個函數是可以依據 type 和該地址值恢復出 Value 的。例如:

func NewAt(typ Type, p unsafe.Pointer) Value

4、從 Value 到 Type

從反射對象 Value 到 Type 可以直接調用 Value 的方法,因為 Value 內部存放著到 Type 類型的指針。例如:

func (v Value) Type() Type

5、從 Value 到實例

Value 本身就包含類型和值信息,reflect 提供瞭豐富的方法來實現從 Value 到實例的轉換。例如:

//該方法最通用,用來將 Value 轉換為空接口,該空接口內部存放具體類型實例
//可以使用接口類型查詢去還原為具體的類型
func (v Value) Interface() (i interface{})

//Value 自身也提供豐富的方法,直接將 Value 轉換為簡單類型實例,如果類型不匹配,則直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64

6、從 Value 的指針到值

從一個指針類型的 Value 獲得值類型 Value 有兩種方法,示例如下。

//如果 v 類型是接口,則 Elem() 返回接口綁定的實例的 Value,如采 v 類型是指針,則返回指針值的 Value,否則引起 panic
func (v Value) Elem() Value
//如果 v 是指針,則返回指針值的 Value,否則返回 v 自身,該函數不會引起 panic
func Indirect(v Value) Value

7、Type 指針和值的相互轉換

指針類型 Type 到值類型 Type。例如:

//t 必須是 Array、Chan、Map、Ptr、Slice,否則會引起 panic
//Elem 返回的是其內部元素的 Type
t.Elem() Type

值類型 Type 到指針類型 Type。例如:

//PtrTo 返回的是指向 t 的指針型 Type
func PtrTo(t Type) Type

8、Value 值的可修改性

Value 值的修改涉及如下兩個方法:

//通過 CanSet 判斷是否能修改
func (v Value ) CanSet() bool
//通過 Set 進行修改
func (v Value ) Set(x Value)

Value 值在什麼情況下可以修改?我們知道實例對象傳遞給接口的是一個完全的值拷貝,如果調用反射的方法 reflect.ValueOf() 傳進去的是一個值類型變量, 則獲得的 Value 實際上是原對象的一個副本,這個 Value 是無論如何也不能被修改的。

9、根據 Go 官方關於反射的文檔,反射有三大定律:9

  • Reflection goes from interface value to reflection object.
  • Reflection goes from reflection object to interface value.
  • To modify a reflection object, the value must be settable.

第一條是最基本的:反射可以從接口值得到反射對象。

反射是一種檢測存儲在 interface中的類型和值機制。這可以通過 TypeOf函數和 ValueOf函數得到。

第二條實際上和第一條是相反的機制,反射可以從反射對象獲得接口值。

它將 ValueOf的返回值通過 Interface()函數反向轉變成 interface變量。

前兩條就是說 接口型變量和 反射類型對象可以相互轉化,反射類型對象實際上就是指的前面說的 reflect.Type和 reflect.Value。

第三條不太好懂:如果需要操作一個反射變量,則其值必須可以修改。

反射變量可設置的本質是它存儲瞭原變量本身,這樣對反射變量的操作,就會反映到原變量本身;反之,如果反射變量不能代表原變量,那麼操作瞭反射變量,不會對原變量產生任何影響,這會給使用者帶來疑惑。所以第二種情況在語言層面是不被允許的。

二、反射的使用

從relfect.Value中獲取接口interface的信息

當執行reflect.ValueOf(interface)之後,就得到瞭一個類型為”relfect.Value”變量,可以通過它本身的Interface()方法獲得接口變量的真實內容,然後可以通過類型判斷進行轉換,轉換為原有真實類型。不過,我們可能是已知原有類型,也有可能是未知原有類型,因此,下面分兩種情況進行說明。

1、已知原有類型

已知類型後轉換為其對應的類型的做法如下,直接通過Interface方法然後強制轉換,如下:

realValue := value.Interface().(已知的類型)

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var num float64 = 3.1415926

    pointer := reflect.ValueOf(&num)
    value := reflect.ValueOf(num)

    // 可以理解為“強制轉換”,但是需要註意的時候,轉換的時候,如果轉換的類型不完全符合,則直接panic
    // Golang 對類型要求非常嚴格,類型一定要完全符合
    // 如下兩個,一個是*float64,一個是float64,如果弄混,則會panic
    convertPointer := pointer.Interface().(*float64)
    convertValue := value.Interface().(float64)

    fmt.Println(convertPointer)
    fmt.Println(convertValue)
}

運行結果:

0xc000018080
3.1415926

說明

  • 轉換的時候,如果轉換的類型不完全符合,則直接panic,類型要求非常嚴格!
  • 轉換的時候,要區分是指針還是指
  • 也就是說反射可以將“反射類型對象”再重新轉換為“接口類型變量”

2、未知原有類型

很多情況下,我們可能並不知道其具體類型,那麼這個時候,該如何做呢?需要我們進行遍歷探測其Filed來得知,示例如下:

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string
    Age int
    Sex string
}

func (p Person)Say(msg string)  {
    fmt.Println("hello,",msg)
}
func (p Person)PrintInfo()  {
    fmt.Printf("姓名:%s,年齡:%d,性別:%s\n",p.Name,p.Age,p.Sex)
}

func main() {
    p1 := Person{"王富貴",20,"男"}

    DoFiledAndMethod(p1)

}

// 通過接口來獲取任意參數
func DoFiledAndMethod(input interface{}) {

    getType := reflect.TypeOf(input) //先獲取input的類型
    fmt.Println("get Type is :", getType.Name()) // Person
    fmt.Println("get Kind is : ", getType.Kind()) // struct

    getValue := reflect.ValueOf(input)
    fmt.Println("get all Fields is:", getValue) //{王富貴 20 男}

    // 獲取方法字段
    // 1. 先獲取interface的reflect.Type,然後通過NumField進行遍歷
    // 2. 再通過reflect.Type的Field獲取其Field
    // 3. 最後通過Field的Interface()得到對應的value
    for i := 0; i < getType.NumField(); i++ {
        field := getType.Field(i)
        value := getValue.Field(i).Interface() //獲取第i個值
        fmt.Printf("字段名稱:%s, 字段類型:%s, 字段數值:%v \n", field.Name, field.Type, value)
    }

    // 通過反射,操作方法
    // 1. 先獲取interface的reflect.Type,然後通過.NumMethod進行遍歷
    // 2. 再公國reflect.Type的Method獲取其Method
    for i := 0; i < getType.NumMethod(); i++ {
        method := getType.Method(i)
        fmt.Printf("方法名稱:%s, 方法類型:%v \n", method.Name, method.Type)
    }
}

運行結果:

get Type is : Person
get Kind is :  struct
get all Fields is: {王富貴 20 男}
字段名稱:Name, 字段類型:string, 字段數值:王富貴 
字段名稱:Age, 字段類型:int, 字段數值:20 
字段名稱:Sex, 字段類型:string, 字段數值:男 
方法名稱:PrintInfo, 方法類型:func(main.Person) 
方法名稱:Say, 方法類型:func(main.Person, string) 

總結

獲取未知類型的interface的具體變量及其類型的步驟為:

  • 先獲取interface的reflect.Type,然後通過NumField進行遍歷
  • 再通過reflect.Type的Field獲取其Field
  • 最後通過Field的Interface()得到對應的value

獲取未知類型的interface的所屬方法(函數)的步驟為:

  • 先獲取interface的reflect.Type,然後通過NumMethod進行遍歷
  • 再分別通過reflect.Type的Method獲取對應的真實的方法(函數)
  • 最後對結果取其Name和Type得知具體的方法名
  • 也就是說反射可以將“反射類型對象”再重新轉換為“接口類型變量”
  • struct 或者 struct 的嵌套都是一樣的判斷處理方式

以上就是Go利用反射reflect實現獲取接口變量信息的詳細內容,更多關於Go reflect獲取接口信息的資料請關註WalkonNet其它相關文章!

推薦閱讀: