12.2. reflect.Type 和 reflect.Value
反射是由 reflect 包提供的。它定義了兩個重要的類型,Type 和 Value。一個 Type 表示一個Go類型。它是一個接口,有許多方法來區分類型以及檢查它們的組成部分,例如一個結構體的成員或一個函數的參數等。唯一能反映 reflect.Type 實現的是接口的類型描述信息(§7.5),也正是這個實體標識了接口值的動態類型。
函數 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{} 參數。回到 7.5節 的將一個具體的值轉為接口類型會有一個隱式的接口轉換操作,它會創建一個包含兩個信息的接口值:操作數的動態類型(這裡是 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\n", 3) // "int"
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\n", 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{}
i := x.(int) // an int
fmt.Printf("%d\n", 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。)
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"
}
}
到目前為止,我們的函數將每個值視作一個不可分割沒有內部結構的物品,因此它叫 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"