溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go如何利用反射reflect實(shí)現(xiàn)獲取接口變量信息

發(fā)布時(shí)間:2022-05-30 09:50:03 來源:億速云 閱讀:181 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Go如何利用反射reflect實(shí)現(xiàn)獲取接口變量信息”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Go如何利用反射reflect實(shí)現(xiàn)獲取接口變量信息”吧!

    引言

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

    一、反射的規(guī)則

    其實(shí)反射的操作步驟非常的簡(jiǎn)單,就是通過實(shí)體對(duì)象獲取反射對(duì)象(Value、Type),然后操作相應(yīng)的方法即可。

    下圖描述了實(shí)例、Value、Type 三者之間的轉(zhuǎn)換關(guān)系:

    Go如何利用反射reflect實(shí)現(xiàn)獲取接口變量信息

    反射 API 的分類總結(jié)如下:

    1、從實(shí)例到 Value

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

    func ValueOf(i interface {}) Value

    2、從實(shí)例到 Type

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

    func TypeOf(i interface{}) Type

    3、從 Type 到 Value

    Type 里面只有類型信息,所以直接從一個(gè) Type 接口變量里面是無法獲得實(shí)例的 Value 的,但可以通過該 Type 構(gòu)建一個(gè)新實(shí)例的 Value。reflect 包提供了兩種方法,示例如下:

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

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

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

    4、從 Value 到 Type

    從反射對(duì)象 Value 到 Type 可以直接調(diào)用 Value 的方法,因?yàn)?Value 內(nèi)部存放著到 Type 類型的指針。例如:

    func (v Value) Type() Type

    5、從 Value 到實(shí)例

    Value 本身就包含類型和值信息,reflect 提供了豐富的方法來實(shí)現(xiàn)從 Value 到實(shí)例的轉(zhuǎn)換。例如:

    //該方法最通用,用來將 Value 轉(zhuǎn)換為空接口,該空接口內(nèi)部存放具體類型實(shí)例
    //可以使用接口類型查詢?nèi)ミ€原為具體的類型
    func (v Value) Interface() (i interface{})
    
    //Value 自身也提供豐富的方法,直接將 Value 轉(zhuǎn)換為簡(jiǎn)單類型實(shí)例,如果類型不匹配,則直接引起 panic
    func (v Value) Bool () bool
    func (v Value) Float() float64
    func (v Value) Int() int64
    func (v Value) Uint() uint64

    6、從 Value 的指針到值

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

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

    7、Type 指針和值的相互轉(zhuǎn)換

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

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

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

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

    8、Value 值的可修改性

    Value 值的修改涉及如下兩個(gè)方法:

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

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

    9、根據(jù) Go 官方關(guān)于反射的文檔,反射有三大定律: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.

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

    反射是一種檢測(cè)存儲(chǔ)在 interface中的類型和值機(jī)制。這可以通過 TypeOf函數(shù)和 ValueOf函數(shù)得到。

    第二條實(shí)際上和第一條是相反的機(jī)制,反射可以從反射對(duì)象獲得接口值。

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

    前兩條就是說 接口型變量和 反射類型對(duì)象可以相互轉(zhuǎn)化,反射類型對(duì)象實(shí)際上就是指的前面說的 reflect.Type和 reflect.Value。

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

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

    二、反射的使用

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

    當(dāng)執(zhí)行reflect.ValueOf(interface)之后,就得到了一個(gè)類型為”relfect.Value”變量,可以通過它本身的Interface()方法獲得接口變量的真實(shí)內(nèi)容,然后可以通過類型判斷進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換為原有真實(shí)類型。不過,我們可能是已知原有類型,也有可能是未知原有類型,因此,下面分兩種情況進(jìn)行說明。

    1、已知原有類型

    已知類型后轉(zhuǎn)換為其對(duì)應(yīng)的類型的做法如下,直接通過Interface方法然后強(qiáng)制轉(zhuǎn)換,如下:

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

    示例代碼:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var num float64 = 3.1415926
    
        pointer := reflect.ValueOf(&num)
        value := reflect.ValueOf(num)
    
        // 可以理解為“強(qiáng)制轉(zhuǎn)換”,但是需要注意的時(shí)候,轉(zhuǎn)換的時(shí)候,如果轉(zhuǎn)換的類型不完全符合,則直接panic
        // Golang 對(duì)類型要求非常嚴(yán)格,類型一定要完全符合
        // 如下兩個(gè),一個(gè)是*float64,一個(gè)是float64,如果弄混,則會(huì)panic
        convertPointer := pointer.Interface().(*float64)
        convertValue := value.Interface().(float64)
    
        fmt.Println(convertPointer)
        fmt.Println(convertValue)
    }

    運(yùn)行結(jié)果:

    0xc000018080
    3.1415926

    說明

    • 轉(zhuǎn)換的時(shí)候,如果轉(zhuǎn)換的類型不完全符合,則直接panic,類型要求非常嚴(yán)格!

    • 轉(zhuǎn)換的時(shí)候,要區(qū)分是指針還是指

    • 也就是說反射可以將“反射類型對(duì)象”再重新轉(zhuǎn)換為“接口類型變量”

    2、未知原有類型

    很多情況下,我們可能并不知道其具體類型,那么這個(gè)時(shí)候,該如何做呢?需要我們進(jìn)行遍歷探測(cè)其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)
    
    }
    
    // 通過接口來獲取任意參數(shù)
    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進(jìn)行遍歷
        // 2. 再通過reflect.Type的Field獲取其Field
        // 3. 最后通過Field的Interface()得到對(duì)應(yīng)的value
        for i := 0; i < getType.NumField(); i++ {
            field := getType.Field(i)
            value := getValue.Field(i).Interface() //獲取第i個(gè)值
            fmt.Printf("字段名稱:%s, 字段類型:%s, 字段數(shù)值:%v \n", field.Name, field.Type, value)
        }
    
        // 通過反射,操作方法
        // 1. 先獲取interface的reflect.Type,然后通過.NumMethod進(jìn)行遍歷
        // 2. 再公國(guó)reflect.Type的Method獲取其Method
        for i := 0; i < getType.NumMethod(); i++ {
            method := getType.Method(i)
            fmt.Printf("方法名稱:%s, 方法類型:%v \n", method.Name, method.Type)
        }
    }

    運(yùn)行結(jié)果:

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

    到此,相信大家對(duì)“Go如何利用反射reflect實(shí)現(xiàn)獲取接口變量信息”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI