新聞中心
反射(reflection)是在 Java 出現(xiàn)后迅速流行起來(lái)的一種概念,通過(guò)反射可以獲取豐富的類型信息,并可以利用這些類型信息做非常靈活的工作。

大多數(shù)現(xiàn)代的高級(jí)語(yǔ)言都以各種形式支持反射功能,反射是把雙刃劍,功能強(qiáng)大但代碼可讀性并不理想,若非必要并不推薦使用反射。
下面我們就來(lái)將介紹一下反射在Go語(yǔ)言中的具體體現(xiàn)以及反射的基本使用方法。
反射的基本概念
Go語(yǔ)言提供了一種機(jī)制在運(yùn)行時(shí)更新和檢查變量的值、調(diào)用變量的方法和變量支持的內(nèi)在操作,但是在編譯時(shí)并不知道這些變量的具體類型,這種機(jī)制被稱為反射。反射也可以讓我們將類型本身作為第一類的值類型處理。
反射是指在程序運(yùn)行期對(duì)程序本身進(jìn)行訪問(wèn)和修改的能力,程序在編譯時(shí)變量被轉(zhuǎn)換為內(nèi)存地址,變量名不會(huì)被編譯器寫入到可執(zhí)行部分,在運(yùn)行程序時(shí)程序無(wú)法獲取自身的信息。
支持反射的語(yǔ)言可以在程序編譯期將變量的反射信息,如字段名稱、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中,并給程序提供接口訪問(wèn)反射信息,這樣就可以在程序運(yùn)行期獲取類型的反射信息,并且有能力修改它們。
C/ C++語(yǔ)言沒(méi)有支持反射功能,只能通過(guò) typeid 提供非常弱化的程序運(yùn)行時(shí)類型信息;Java、 C# 等語(yǔ)言都支持完整的反射功能;Lua、 JavaScript 類動(dòng)態(tài)語(yǔ)言,由于其本身的語(yǔ)法特性就可以讓代碼在運(yùn)行期訪問(wèn)程序自身的值和類型信息,因此不需要反射系統(tǒng)。
Go語(yǔ)言程序的反射系統(tǒng)無(wú)法獲取到一個(gè)可執(zhí)行文件空間中或者是一個(gè)包中的所有類型信息,需要配合使用標(biāo)準(zhǔn)庫(kù)中對(duì)應(yīng)的詞法、語(yǔ)法解析器和抽象語(yǔ)法樹(shù)(AST)對(duì)源碼進(jìn)行掃描后獲得這些信息。
Go語(yǔ)言提供了 reflect 包來(lái)訪問(wèn)程序的反射信息。
reflect 包
Go語(yǔ)言中的反射是由 reflect 包提供支持的,它定義了兩個(gè)重要的類型 Type 和 Value 任意接口值在反射中都可以理解為由 reflect.Type 和 reflect.Value 兩部分組成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 兩個(gè)函數(shù)來(lái)獲取任意對(duì)象的 Value 和 Type。
反射的類型對(duì)象(reflect.Type)
在Go語(yǔ)言程序中,使用 reflect.TypeOf() 函數(shù)可以獲得任意值的類型對(duì)象(reflect.Type),程序通過(guò)類型對(duì)象可以訪問(wèn)任意值的類型信息,下面通過(guò)示例來(lái)理解獲取類型對(duì)象的過(guò)程:
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}運(yùn)行結(jié)果如下:
int int
代碼說(shuō)明如下:
- 第 9 行,定義一個(gè) int 類型的變量。
- 第 10 行,通過(guò) reflect.TypeOf() 取得變量 a 的類型對(duì)象 typeOfA,類型為 reflect.Type()。
- 第 11 行中,通過(guò) typeOfA 類型對(duì)象的成員函數(shù),可以分別獲取到 typeOfA 變量的類型名為 int,種類(Kind)為 int。
反射的類型(Type)與種類(Kind)
在使用反射時(shí),需要首先理解類型(Type)和種類(Kind)的區(qū)別。編程中,使用最多的是類型,但在反射中,當(dāng)需要區(qū)分一個(gè)大品種的類型時(shí),就會(huì)用到種類(Kind)。例如需要統(tǒng)一判斷類型中的指針時(shí),使用種類(Kind)信息就較為方便。
1) 反射種類(Kind)的定義
Go語(yǔ)言程序中的類型(Type)指的是系統(tǒng)原生數(shù)據(jù)類型,如 int、string、bool、float32 等類型,以及使用 type 關(guān)鍵字定義的類型,這些類型的名稱就是其類型本身的名稱。例如使用 type A struct{} 定義結(jié)構(gòu)體時(shí),A 就是 struct{} 的類型。
種類(Kind)指的是對(duì)象歸屬的品種,在 reflect 包中有如下定義:
type Kind uint
const (
Invalid Kind = iota // 非法類型
Bool // 布爾型
Int // 有符號(hào)整型
Int8 // 有符號(hào)8位整型
Int16 // 有符號(hào)16位整型
Int32 // 有符號(hào)32位整型
Int64 // 有符號(hào)64位整型
Uint // 無(wú)符號(hào)整型
Uint8 // 無(wú)符號(hào)8位整型
Uint16 // 無(wú)符號(hào)16位整型
Uint32 // 無(wú)符號(hào)32位整型
Uint64 // 無(wú)符號(hào)64位整型
Uintptr // 指針
Float32 // 單精度浮點(diǎn)數(shù)
Float64 // 雙精度浮點(diǎn)數(shù)
Complex64 // 64位復(fù)數(shù)類型
Complex128 // 128位復(fù)數(shù)類型
Array // 數(shù)組
Chan // 通道
Func // 函數(shù)
Interface // 接口
Map // 映射
Ptr // 指針
Slice // 切片
String // 字符串
Struct // 結(jié)構(gòu)體
UnsafePointer // 底層指針
) Map、Slice、Chan 屬于引用類型,使用起來(lái)類似于指針,但是在種類常量定義中仍然屬于獨(dú)立的種類,不屬于 Ptr。type A struct{} 定義的結(jié)構(gòu)體屬于 Struct 種類,*A 屬于 Ptr。
2) 從類型對(duì)象中獲取類型名稱和種類
Go語(yǔ)言中的類型名稱對(duì)應(yīng)的反射獲取方法是 reflect.Type 中的 Name() 方法,返回表示類型名稱的字符串;類型歸屬的種類(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 類型的常量。
下面的代碼中會(huì)對(duì)常量和結(jié)構(gòu)體進(jìn)行類型信息獲取。
package main
import (
"fmt"
"reflect"
)
// 定義一個(gè)Enum類型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 聲明一個(gè)空結(jié)構(gòu)體
type cat struct {
}
// 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfCat := reflect.TypeOf(cat{})
// 顯示反射類型對(duì)象的名稱和種類
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
// 獲取Zero常量的反射類型對(duì)象
typeOfA := reflect.TypeOf(Zero)
// 顯示反射類型對(duì)象的名稱和種類
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}運(yùn)行結(jié)果如下:
cat struct
Enum int
代碼說(shuō)明如下:
- 第 17 行,聲明結(jié)構(gòu)體類型 cat。
- 第 20 行,將 cat 實(shí)例化,并且使用 reflect.TypeOf() 獲取被實(shí)例化后的 cat 的反射類型對(duì)象。
- 第 22 行,輸出 cat 的類型名稱和種類,類型名稱就是 cat,而 cat 屬于一種結(jié)構(gòu)體種類,因此種類為 struct。
- 第 24 行,Zero 是一個(gè) Enum 類型的常量。這個(gè) Enum 類型在第 9 行聲明,第 12 行聲明了常量。如沒(méi)有常量也不能創(chuàng)建實(shí)例,通過(guò) reflect.TypeOf() 直接獲取反射類型對(duì)象。
- 第 26 行,輸出 Zero 對(duì)應(yīng)的類型對(duì)象的類型名和種類。
指針與指針指向的元素
Go語(yǔ)言程序中對(duì)指針獲取反射對(duì)象時(shí),可以通過(guò) reflect.Elem() 方法獲取這個(gè)指針指向的元素類型,這個(gè)獲取過(guò)程被稱為取元素,等效于對(duì)指針類型變量做了一個(gè)
*操作,代碼如下:
package main
import (
"fmt"
"reflect"
)
func main() {
// 聲明一個(gè)空結(jié)構(gòu)體
type cat struct {
}
// 創(chuàng)建cat的實(shí)例
ins := &cat{}
// 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfCat := reflect.TypeOf(ins)
// 顯示反射類型對(duì)象的名稱和種類
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
// 取類型的元素
typeOfCat = typeOfCat.Elem()
// 顯示反射類型對(duì)象的名稱和種類
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}運(yùn)行結(jié)果如下:
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'
代碼說(shuō)明如下:
- 第 13 行,創(chuàng)建了 cat 結(jié)構(gòu)體的實(shí)例,ins 是一個(gè) *cat 類型的指針變量。
- 第 15 行,對(duì)指針變量獲取反射類型信息。
- 第 17 行,輸出指針變量的類型名稱和種類。Go語(yǔ)言的反射中對(duì)所有指針變量的種類都是 Ptr,但需要注意的是,指針變量的類型名稱是空,不是 *cat。
- 第 19 行,取指針類型的元素類型,也就是 cat 類型。這個(gè)操作不可逆,不可以通過(guò)一個(gè)非指針類型獲取它的指針類型。
- 第 21 行,輸出指針變量指向元素的類型名稱和種類,得到了 cat 的類型名稱(cat)和種類(struct)。
使用反射獲取結(jié)構(gòu)體的成員類型
任意值通過(guò) reflect.TypeOf() 獲得反射對(duì)象信息后,如果它的類型是結(jié)構(gòu)體,可以通過(guò)反射值對(duì)象 reflect.Type 的 NumField() 和 Field() 方法獲得結(jié)構(gòu)體成員的詳細(xì)信息。
與成員獲取相關(guān)的 reflect.Type 的方法如下表所示。
| 方法 | 說(shuō)明 |
|---|---|
| Field(i int) StructField | 根據(jù)索引返回索引對(duì)應(yīng)的結(jié)構(gòu)體字段的信息,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
| NumField() int | 返回結(jié)構(gòu)體成員字段數(shù)量,當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
| FieldByName(name string) (StructField, bool) | 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段的信息,沒(méi)有找到時(shí) bool 返回 false,當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
| FieldByIndex(index []int) StructField | 多層成員訪問(wèn)時(shí),根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引,返回字段的信息,沒(méi)有找到時(shí)返回零值。當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
| FieldByNameFunc(match func(string) bool) (StructField,bool) | 根據(jù)匹配函數(shù)匹配需要的字段,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī) |
1) 結(jié)構(gòu)體字段類型
reflect.Type 的 Field() 方法返回 StructField 結(jié)構(gòu),這個(gè)結(jié)構(gòu)描述結(jié)構(gòu)體的成員信息,通過(guò)這個(gè)信息可以獲取成員與結(jié)構(gòu)體的關(guān)系,如偏移、索引、是否為匿名字段、結(jié)構(gòu)體標(biāo)簽(StructTag)等,而且還可以通過(guò) StructField 的 Type 字段進(jìn)一步獲取結(jié)構(gòu)體成員的類型信息。
StructField 的結(jié)構(gòu)如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路徑
Type Type // 字段反射類型對(duì)象
Tag StructTag // 字段的結(jié)構(gòu)體標(biāo)簽
Offset uintptr // 字段在結(jié)構(gòu)體中的相對(duì)偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否為匿名字段
} 字段說(shuō)明如下:
- Name:為字段名稱。
- PkgPath:字段在結(jié)構(gòu)體中的路徑。
- Type:字段本身的反射類型對(duì)象,類型為 reflect.Type,可以進(jìn)一步獲取字段的類型信息。
- Tag:結(jié)構(gòu)體標(biāo)簽,為結(jié)構(gòu)體字段標(biāo)簽的額外信息,可以單獨(dú)提取。
- Index:FieldByIndex 中的索引順序。
- Anonymous:表示該字段是否為匿名字段。
2) 獲取成員反射信息
下面代碼中,實(shí)例化一個(gè)結(jié)構(gòu)體并遍歷其結(jié)構(gòu)體成員,再通過(guò) reflect.Type 的 FieldByName() 方法查找結(jié)構(gòu)體中指定名稱的字段,直接獲取其類型信息。
反射訪問(wèn)結(jié)構(gòu)體成員類型及信息:
package main
import (
"fmt"
"reflect"
)
func main() {
// 聲明一個(gè)空結(jié)構(gòu)體
type cat struct {
Name string
// 帶有結(jié)構(gòu)體tag的字段
Type int `json:"type" id:"100"`
}
// 創(chuàng)建cat的實(shí)例
ins := cat{Name: "mimi", Type: 1}
// 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
typeOfCat := reflect.TypeOf(ins)
// 遍歷結(jié)構(gòu)體所有成員
for i := 0; i < typeOfCat.NumField(); i++ {
// 獲取每個(gè)成員的結(jié)構(gòu)體字段類型
fieldType := typeOfCat.Field(i)
// 輸出成員名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通過(guò)字段名, 找到字段類型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 從tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}代碼輸出如下:
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
代碼說(shuō)明如下:
- 第 10 行,聲明了帶有兩個(gè)成員的 cat 結(jié)構(gòu)體。
- 第 13 行,Type 是 cat 的一個(gè)成員,這個(gè)成員類型后面帶有一個(gè)以 ` 開(kāi)始和結(jié)尾的字符串。這個(gè)字符串在Go語(yǔ)言中被稱為 Tag(標(biāo)簽)。一般用于給字段添加自定義信息,方便其他模塊根據(jù)信息進(jìn)行不同功能的處理。
- 第 16 行,創(chuàng)建 cat 實(shí)例,并對(duì)兩個(gè)字段賦值。結(jié)構(gòu)體標(biāo)簽屬于類型信息,無(wú)須且不能賦值。
- 第 18 行,獲取實(shí)例的反射類型對(duì)象。
- 第 20 行,使用 reflect.Type 類型的 NumField() 方法獲得一個(gè)結(jié)構(gòu)體類型共有多少個(gè)字段。如果類型不是結(jié)構(gòu)體,將會(huì)觸發(fā)宕機(jī)錯(cuò)誤。
- 第 22 行,reflect.Type 中的 Field() 方法和 NumField 一般都是配對(duì)使用,用來(lái)實(shí)現(xiàn)結(jié)構(gòu)體成員的遍歷操作。
- 第 24 行,使用 reflect.Type 的 Field() 方法返回的結(jié)構(gòu)不再是 reflect.Type 而是 StructField 結(jié)構(gòu)體。
- 第 27 行,使用 reflect.Type 的 FieldByName() 根據(jù)字段名查找結(jié)構(gòu)體字段信息,catType 表示返回的結(jié)構(gòu)體字段信息,類型為 StructField,ok 表示是否找到結(jié)構(gòu)體字段的信息。
- 第 29 行中,使用 StructField 中 Tag 的 Get() 方法,根據(jù) Tag 中的名字進(jìn)行信息獲取。
結(jié)構(gòu)體標(biāo)簽(Struct Tag)
通過(guò) reflect.Type 獲取結(jié)構(gòu)體成員信息 reflect.StructField 結(jié)構(gòu)中的 Tag 被稱為結(jié)構(gòu)體標(biāo)簽(StructTag)。結(jié)構(gòu)體標(biāo)簽是對(duì)結(jié)構(gòu)體字段的額外信息標(biāo)簽。
JSON、BSON 等格式進(jìn)行序列化及對(duì)象關(guān)系映射(Object Relational Mapping,簡(jiǎn)稱 ORM)系統(tǒng)都會(huì)用到結(jié)構(gòu)體標(biāo)簽,這些系統(tǒng)使用標(biāo)簽設(shè)定字段在處理時(shí)應(yīng)該具備的特殊屬性和可能發(fā)生的行為。這些信息都是靜態(tài)的,無(wú)須實(shí)例化結(jié)構(gòu)體,可以通過(guò)反射獲取到。
1) 結(jié)構(gòu)體標(biāo)簽的格式
Tag 在結(jié)構(gòu)體字段后方書(shū)寫的格式如下:
`key1:"value1" key2:"value2"`
結(jié)構(gòu)體標(biāo)簽由一個(gè)或多個(gè)鍵值對(duì)組成;鍵與值使用冒號(hào)分隔,值用雙引號(hào)括起來(lái);鍵值對(duì)之間使用一個(gè)空格分隔。
2) 從結(jié)構(gòu)體標(biāo)簽中獲取值
StructTag 擁有一些方法,可以進(jìn)行 Tag 信息的解析和提取,如下所示:
-
func (tag StructTag) Get(key string) string:根據(jù) Tag 中的鍵獲取對(duì)應(yīng)的值,例如`key1:"value1" key2:"value2"`的 Tag 中,可以傳入“key1”獲得“value1”。 -
func (tag StructTag) Lookup(key string) (value string, ok bool):根據(jù) Tag 中的鍵,查詢值是否存在。
3) 結(jié)構(gòu)體標(biāo)簽格式錯(cuò)誤導(dǎo)致的問(wèn)題
編寫 Tag 時(shí),必須嚴(yán)格遵守鍵值對(duì)的規(guī)則。結(jié)構(gòu)體標(biāo)簽的解析代碼的容錯(cuò)能力很差,一旦格式寫錯(cuò),編譯和運(yùn)行時(shí)都不會(huì)提示任何錯(cuò)誤,示例代碼如下:
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
Name string
Type int `json: "type" id:"100"`
}
typeOfCat := reflect.TypeOf(cat{})
if catType, ok := typeOfCat.FieldByName("Type"); ok {
fmt.Println(catType.Tag.Get("json"))
}
}運(yùn)行上面的代碼會(huì)輸出一個(gè)空字符串,并不會(huì)輸出期望的 type。
代碼第 11 行中,在 json: 和 "type" 之間增加了一個(gè)空格,這種寫法沒(méi)有遵守結(jié)構(gòu)體標(biāo)簽的規(guī)則,因此無(wú)法通過(guò) Tag.Get 獲取到正確的 json 對(duì)應(yīng)的值。這個(gè)錯(cuò)誤在開(kāi)發(fā)中非常容易被疏忽,造成難以察覺(jué)的錯(cuò)誤。所以將第 12 行代碼修改為下面的樣子,則可以正常打印。
type cat struct {
Name string
Type int `json:"type" id:"100"`
}運(yùn)行結(jié)果如下:
type
網(wǎng)頁(yè)標(biāo)題:創(chuàng)新互聯(lián)GO教程:Go語(yǔ)言反射(reflection)簡(jiǎn)述
網(wǎng)站鏈接:http://www.dlmjj.cn/article/cogpged.html


咨詢
建站咨詢
