Go基礎學習記錄之反射(reflect)機制

DurbanZhang發表於2019-02-16

為什麼需要反射機制,這裡引用網上的原話

有時候我們需要編寫一個函式能夠處理一類並不滿足普通公共介面的型別的值,也可能是因為它們並沒有確定的表示方式,或者是在我們設計該函式的時候這些型別可能還不存在。

一個大家熟悉的例子是fmt.Fprintf函式提供的字串格式化處理邏輯,它可以用來對任意型別的值格式化並列印,甚至支援使用者自定義的型別。讓我們也來嘗試實現一個類似功能的函式。為了簡單起見,我們的函式只接收一個引數,然後返回和fmt.Sprint類似的格式化後的字串。我們實現的函式名也叫Sprint。

我們首先用switch型別分支來測試輸入引數是否實現了String方法,如果是的話就呼叫該方法。然後繼續增加型別測試分支,檢查這個值的動態型別是否是string、int、bool等基礎型別,並在每種情況下執行相應的格式化操作。

至於我為什麼要了解下這個問題,因為我遇到了唄,反射機制對於在使用第三方資料庫的時候很有幫助,能夠幫助我們判斷返回的值,如果處理值,比如我之前分享的文章[Go基礎學習記錄之Web開發的部落格文章列表展示功能]裡面的查詢函式Query,就用到了這個反射,MySQL的庫中的Scan函式需要傳入一個[]interface{},而且值為指標型別,這個時候我們需要根據這個指標獲取到interface{}然後轉為對應的型別的值,最後組成程式需要的結果值返回給控制層來交給View去給到前端展示

reflect.Type和reflect.Value

反射是由 reflect 包提供的。 它定義了兩個重要的型別, Type 和 Value. 一個 Type 表示一個Go型別. 它是一個介面, 有許多方法來區分型別以及檢查它們的組成部分, 例如一個結構體的成員或一個函式的引數等. 唯一能反映 reflect.Type 實現的是介面的型別描述資訊, 也正是這個實體標識了介面值的動態型別.

reflect.Type
函式 reflect.TypeOf 接受任意的 interface{} 型別, 並以reflect.Type形式返回其動態型別:

t := reflect.TypeOf(3)  // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t)          // "int"

其中 TypeOf(3) 呼叫將值 3 傳給 interface{} 引數. 這裡有個隱式的介面的介面,將一個具體的值轉為介面型別會有一個隱式的介面轉換操作, 它會建立一個包含兩個資訊的介面值: 運算元的動態型別(這裡是int)和它的動態的值(這裡是3).

因為 reflect.TypeOf 返回的是一個動態型別的介面值, 它總是返回具體的型別. 因此, 下面的程式碼將列印 *os.File 而不是 “io.Writer”. 稍後, 我們將看到能夠表達介面型別的 reflect.Type.

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"

要注意的是 reflect.Type 介面是滿足 fmt.Stringer 介面的. 因為列印一個介面的動態型別對於除錯和日誌是有幫助的, fmt.Printf 提供了一個縮寫 %T 引數, 內部使用 reflect.TypeOf 來輸出:

fmt.Printf("%T
", 3) // "int"

reflect.Value
reflect 包中另一個重要的型別是 Value. 一個 reflect.Value 可以裝載任意型別的值. 函式 reflect.ValueOf 接受任意的 interface{} 型別, 並返回一個裝載著其動態值的 reflect.Value. 和 reflect.TypeOf 類似, reflect.ValueOf 返回的結果也是具體的型別, 但是 reflect.Value 也可以持有一個介面值.

v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v)          // "3"
fmt.Printf("%v
", v)   // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"

和 reflect.Type 類似, reflect.Value 也滿足 fmt.Stringer 介面, 但是除非 Value 持有的是字串, 否則 String 方法只返回其型別. 而使用 fmt 包的 %v 標誌引數會對 reflect.Values 特殊處理.

對 Value 呼叫 Type 方法將返回具體型別所對應的 reflect.Type:

t := v.Type()           // a reflect.Type
fmt.Println(t.String()) // "int"

reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法. 它返回一個 interface{} 型別,裝載著與 reflect.Value 相同的具體值:

v = reflect.ValueOf(3)  // a reflect.Value
x := v.Interface()      // an interface{}
fmt.Println(x)          // "3"
i := x.(int)            // an int
fmt.Printf("%d
", i)   // "3"

reflect.Value 和 interface{} 都能裝載任意的值. 所不同的是, 一個空的介面隱藏了值內部的表示方式和所有方法, 因此只有我們知道具體的動態型別才能使用型別斷言來訪問內部的值(就像上面那樣), 內部值我們沒法訪問. 相比之下, 一個 Value 則有很多方法來檢查其內容, 無論它的具體型別是什麼. 讓我們再次嘗試實現我們的格式化函式 format.Any.

我們使用 reflect.Value 的 Kind 方法來替代之前的型別 switch. 雖然還是有無窮多的型別, 但是它們的kinds型別卻是有限的: Bool, String 和 所有數字型別的基礎型別; Array 和 Struct 對應的聚合型別; Chan, Func, Ptr, Slice, 和 Map 對應的引用型別; interface 型別; 還有表示空值的 Invalid 型別. (空的 reflect.Value 的 kind 即為 Invalid.)

package format

import (
    "reflect"
    "strconv"
)

// Any formats any value as a string.
func Any(value interface{}) string {
    return formatAtom(reflect.ValueOf(value))
}

// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
    switch v.Kind() {
    case reflect.Invalid:
        return "invalid"
    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16,
        reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return strconv.FormatUint(v.Uint(), 10)
    // ...floating-point and complex cases omitted for brevity...
    case reflect.Bool:
        return strconv.FormatBool(v.Bool())
    case reflect.String:
        return strconv.Quote(v.String())
    case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
        return v.Type().String() + " 0x" +
            strconv.FormatUint(uint64(v.Pointer()), 16)
    default: // reflect.Array, reflect.Struct, reflect.Interface
        return v.Type().String() + " value"
    }
}

到目前為止, 我們的函式將每個值視作一個不可分割沒有內部結構的物品, 因此它叫 formatAtom. 對於聚合型別(結構體和陣列)和介面,只是列印值的型別, 對於引用型別(channels, functions, pointers, slices, 和 maps), 列印型別和十六進位制的引用地址. 雖然還不夠理想, 但是依然是一個重大的進步, 並且 Kind 只關心底層表示, format.Any 也支援具名型別. 例如:

var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x))                  // "1"
fmt.Println(format.Any(d))                  // "1"
fmt.Println(format.Any([]int64{x}))         // "[]int64 0x8202b87b0"
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"

資料查詢

http://www.gopl.io/
http://golang-china.github.io…

如果想看具體資訊的話可以轉到這裡

https://docs.hacknode.org/gop…

相關文章