Go 語言 reflect.Type 和 reflect.Value

2023-03-14 17:00 更新

原文鏈接:https://gopl-zh.github.io/ch12/ch12-02.html


12.2. reflect.Type 和 reflect.Value

反射是由 reflect 包提供的。它定義了兩個重要的類型,Type 和 Value。一個 Type 表示一個Go類型。它是一個接口,有許多方法來區(qū)分類型以及檢查它們的組成部分,例如一個結(jié)構(gòu)體的成員或一個函數(shù)的參數(shù)等。唯一能反映 reflect.Type 實現(xiàn)的是接口的類型描述信息(§7.5),也正是這個實體標識了接口值的動態(tài)類型。

函數(shù) reflect.TypeOf 接受任意的 interface{} 類型,并以 reflect.Type 形式返回其動態(tài)類型:

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

其中 TypeOf(3) 調(diào)用將值 3 傳給 interface{} 參數(shù)?;氐?7.5節(jié) 的將一個具體的值轉(zhuǎn)為接口類型會有一個隱式的接口轉(zhuǎn)換操作,它會創(chuàng)建一個包含兩個信息的接口值:操作數(shù)的動態(tài)類型(這里是 int)和它的動態(tài)的值(這里是 3)。

因為 reflect.TypeOf 返回的是一個動態(tài)類型的接口值,它總是返回具體的類型。因此,下面的代碼將打印 "*os.File" 而不是 "io.Writer"。稍后,我們將看到能夠表達接口類型的 reflect.Type。

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

要注意的是 reflect.Type 接口是滿足 fmt.Stringer 接口的。因為打印一個接口的動態(tài)類型對于調(diào)試和日志是有幫助的, fmt.Printf 提供了一個縮寫 %T 參數(shù),內(nèi)部使用 reflect.TypeOf 來輸出:

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

reflect 包中另一個重要的類型是 Value。一個 reflect.Value 可以裝載任意類型的值。函數(shù) reflect.ValueOf 接受任意的 interface{} 類型,并返回一個裝載著其動態(tài)值的 reflect.Value。和 reflect.TypeOf 類似,reflect.ValueOf 返回的結(jié)果也是具體的類型,但是 reflect.Value 也可以持有一個接口值。

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

和 reflect.Type 類似,reflect.Value 也滿足 fmt.Stringer 接口,但是除非 Value 持有的是字符串,否則 String 方法只返回其類型。而使用 fmt 包的 %v 標志參數(shù)會對 reflect.Values 特殊處理。

對 Value 調(diào)用 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{}
i := x.(int)            // an int
fmt.Printf("%d\n", i)   // "3"

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

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

gopl.io/ch12/format

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"
    }
}

到目前為止,我們的函數(shù)將每個值視作一個不可分割沒有內(nèi)部結(jié)構(gòu)的物品,因此它叫 formatAtom。對于聚合類型(結(jié)構(gòu)體和數(shù)組)和接口,只是打印值的類型,對于引用類型(channels、functions、pointers、slices 和 maps),打印類型和十六進制的引用地址。雖然還不夠理想,但是依然是一個重大的進步,并且 Kind 只關(guān)心底層表示,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.Duration6116161)) // "[]time.Duration 0x8202b87e0"


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號