## 12.2. reflect.Type和reflect.Value
反射是由 reflect 包提供的。 它定義了兩個重要的類型, Type 和 Value. 一個 Type 表示一個Go類型. 它是一個接口, 有許多方法來區分類型以及檢查它們的組成部分, 例如一個結構體的成員或一個函數的參數等. 唯一能反映 reflect.Type 實現的是接口的類型描述信息(§7.5), 也正是這個實體標識了接口值的動態類型.
函數 reflect.TypeOf 接受任意的 interface{} 類型, 并以reflect.Type形式返回其動態類型:
```Go
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.
```Go
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"
```
要注意的是 reflect.Type 接口是滿足 fmt.Stringer 接口的. 因為打印一個接口的動態類型對于調試和日志是有幫助的, fmt.Printf 提供了一個縮寫 %T 參數, 內部使用 reflect.TypeOf 來輸出:
```Go
fmt.Printf("%T\n", 3) // "int"
```
reflect 包中另一個重要的類型是 Value. 一個 reflect.Value 可以裝載任意類型的值. 函數 reflect.ValueOf 接受任意的 interface{} 類型, 并返回一個裝載著其動態值的 reflect.Value. 和 reflect.TypeOf 類似, reflect.ValueOf 返回的結果也是具體的類型, 但是 reflect.Value 也可以持有一個接口值.
```Go
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:
```Go
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
```
reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法. 它返回一個 interface{} 類型,裝載著與 reflect.Value 相同的具體值:
```Go
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.)
<u><i>gopl.io/ch12/format</i></u>
```Go
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 也支持具名類型. 例如:
```Go
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"
```
- 前言
- Go語言起源
- Go語言項目
- 本書的組織
- 更多的信息
- 致謝
- 入門
- Hello, World
- 命令行參數
- 查找重復的行
- GIF動畫
- 獲取URL
- 并發獲取多個URL
- Web服務
- 本章要點
- 程序結構
- 命名
- 聲明
- 變量
- 賦值
- 類型
- 包和文件
- 作用域
- 基礎數據類型
- 整型
- 浮點數
- 復數
- 布爾型
- 字符串
- 常量
- 復合數據類型
- 數組
- Slice
- Map
- 結構體
- JSON
- 文本和HTML模板
- 函數
- 函數聲明
- 遞歸
- 多返回值
- 錯誤
- 函數值
- 匿名函數
- 可變參數
- Deferred函數
- Panic異常
- Recover捕獲異常
- 方法
- 方法聲明
- 基于指針對象的方法
- 通過嵌入結構體來擴展類型
- 方法值和方法表達式
- 示例: Bit數組
- 封裝
- 接口
- 接口是合約
- 接口類型
- 實現接口的條件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表達式求值
- 類型斷言
- 基于類型斷言識別錯誤類型
- 通過類型斷言查詢接口
- 類型分支
- 示例: 基于標記的XML解碼
- 補充幾點
- Goroutines和Channels
- Goroutines
- 示例: 并發的Clock服務
- 示例: 并發的Echo服務
- Channels
- 并發的循環
- 示例: 并發的Web爬蟲
- 基于select的多路復用
- 并發的退出
- 示例: 聊天服務
- 基于共享變量的并發
- 競爭條件
- sync.Mutex互斥鎖
- sync.RWMutex讀寫鎖
- 內存同步
- 競爭條件檢測
- 示例: 并發的非阻塞緩存
- Goroutines和線程
- 包和工具
- 包簡介
- 導入路徑
- 包聲明
- 導入聲明
- 包的匿名導入
- 包和命名
- 工具
- 測試
- go test
- 測試函數
- 測試覆蓋率
- 基準測試
- 剖析
- 示例函數
- 反射
- 為何需要反射?
- reflect.Type和reflect.Value
- Display遞歸打印
- 示例: 編碼S表達式
- 通過reflect.Value修改值
- 示例: 解碼S表達式
- 顯示一個類型的方法集
- 幾點忠告
- 底層編程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判斷
- 通過cgo調用C代碼
- 幾點忠告
- 附錄
- 附錄A:原文勘誤
- 附錄B:作者譯者
- 附錄C:譯文授權
- 附錄D:其它語言