首頁 > 軟體

Go利用反射reflect實現獲取介面變數資訊

2022-05-28 22:01:21

引言

反射是通過實體物件獲取反射物件(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,性別:%sn",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獲取介面資訊的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com