反射
反射是一種在運行時檢查語言自身結構的機制,它可以很靈活的去應對一些問題,但同時帶來的弊端也很明顯,例如性能問題等等。在 Go 中,反射與interface{}密切相關,很大程度上,只要有interface{}出現的地方,就會有反射。Go 中的反射 API 是由標准庫reflect包提供的。
接口
在開始之前先簡單的了解一下位於runtime包下的兩個接口。在 Go 中,接口本質上是結構體,Go 在運行時將接口分為了兩大類,一類是沒有方法集的接口,另一個類則是有方法集的接口。對於含有方法集的接口來說,在運行時由如下的結構體iface來進行表示
type iface struct {
tab *itab // 包含 數據類型,接口類型,方法集等
data unsafe.Pointer // 指向值的指針
}而對於沒有方法集接口來說,在運行時由eface 結構體來進行表示,如下
type eface struct {
_type *_type // 類型
data unsafe.Pointer // 指向值的指針
}而這兩個結構體在reflect包下都有與其對應的結構體類型,iface對應的是nonEmptyInterface
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // 靜態接口類型
typ *rtype // 動態具體類型
hash uint32 // 類型哈希
_ [4]byte
fun [100000]unsafe.Pointer // 方法集
}
word unsafe.Pointer // 指向值的指針
}而eface對應的是emptyInterface
type emptyInterface struct {
typ *rtype // 動態具體類型
word unsafe.Pointer // 指向指針的值
}對於這兩種類型,官方給出了很明確的定義
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface:emptyInterface is the header for an interface{} value
上述提到了動態具體類型這一詞,原文為dynamic concrete type,首先 Go 語言是一個百分之百的靜態類型語言,靜態這一詞是體現在對外表現的抽象的接口類型是不變的,而動態表示是接口底層存儲的具體實現的類型是可以變化的。至此,對於接口的簡單原理只需要了解到這裡就足夠滿足後續反射的學習。
橋梁
在reflect包下,分別有reflect.Type接口類型來表示 Go 中的類型,reflect.Value結構體類型來表示 Go 中的值
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}上面的代碼省略了很多細節,先只需要了解這兩個類型的存在即可。Go 中所有反射相關的操作都是基於這兩個類型,reflect包提供了兩個函數來將 Go 中的類型轉換為上述的兩種類型以便進行反射操作,分別是reflect.TypeOf函數
func TypeOf(i any) Type與reflect.ValueOf函數
func ValueOf(i any) Value可以看到兩個函數的參數類型都是any,也就是interface{}的別名。如果想要進行反射操作,就需要先將其類型轉換為interface{},這也是為什麼前面提到了只要有反射就離不開空接口。不嚴謹的說,空接口就是連接 Go 類型系統與反射的橋梁,下圖很形象的描述了其過程。

TIP
下文中為了方便,統一使用別名any來替代interface{}
核心
在 Go 中有三個經典的反射定律,結合上面所講的內容也就非常好懂,分別如下
反射可以將
interface{}類型變量轉換成反射對象反射可以將反射對象還原成
interface{}類型變量要修改反射對象,其值必須是可設置的
這三個定律便是 Go 反射的核心,當需要訪問類型相關信息時,便需要用到reflect.TypeOf,當需要修改反射值時,就需要用到reflect.ValueOf
類型
reflect.Type代表著 Go 中的類型,使用reflect.TypeOf()函數可以將變量轉換成reflect.Type。代碼示例如下
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}輸出結果為
stringKind
對於Type而言,Go 內部使用reflect.Kind來表示 Go 中的基礎類型,其本質上是無符號整型uint。
type Kind uintreflect包使用Kind枚舉出了 Go 中所有的基礎類型,如下所示
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)Kind類型僅僅實現了Stringer接口的String()方法,該類型也僅有這一個方法,String()方法的返回值來自於一個其內部的 slice,如下所示,這種寫法乍一看很像 map,但其實這是 Go 中的一種特殊的寫法:索引初始化語法(index expressions in slice literals)
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}type Type interface{
Kind() Kind
}通過Kind,可以知曉空接口存儲的值究竟是什麼基礎類型,例如
func main() {
// 聲明一個any類型的變量
var eface any
// 賦值
eface = 100
// 通過Kind方法,來獲取其類型
fmt.Println(reflect.TypeOf(eface).Kind())
}輸出結果
intElem
type Type interface{
Elem() Type
}使用Type.Elem()方法,可以判斷類型為any的數據結構所存儲的元素類型,可接收的底層參數類型必須是指針,切片,數組,通道,映射表其中之一,否則會panic。下面是代碼示例
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key()會返回map的鍵反射類型
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}輸出為
string
int指針也可以理解為是一個容器,對於指針使用Elem()會獲得其指向元素的反射類型,代碼示例如下
func main() {
var eface any
// 賦值指針
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// 拿到指針所指向元素的反射類型
vType := rType.Elem()
// 輸出包路徑
fmt.Println(vType.PkgPath())
// 輸出其名稱
fmt.Println(vType.Name())
}strings
Builder對於數組,切片,通道用使用起來都是類似的。
Size
type Type interface{
Size() uintptr
}通過Size方法可以獲取對應類型所佔的字節大小,示例如下
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}輸出結果為
8
16
16
8
24TIP
使用unsafe.Sizeof()可以達到同樣的效果
Comparable
type Type interface{
Comparable() bool
}通過Comparable方法可以判斷一個類型是否可以被比較,例子如下
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}輸出如下
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}通過Implements方法可以判斷一個類型是否實現了某一接口
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}輸出結果
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}使用ConvertibleTo方法可以判斷一個類型是否可以被轉換為另一個指定的類型
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}輸出
true
false值
reflect.Value代表著反射接口的值,使用reflect.ValueOf()函數可以將變量轉換成reflect.Value。代碼示例如下
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}輸出結果為
hello world!Type
func (v Value) Type() TypeType方法可以獲取一個反射值的類型
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}輸出
intElem
func (v Value) Elem() Value獲取一個反射值的元素反射值
func main() {
num := new(int)
*num = 114514
// 以指針為例子
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}輸出
114514指針
獲取一個反射值的指針方式有兩種
// 返回一個表示v地址的指針反射值
func (v Value) Addr() Value
// 返回一個指向v的原始值的uinptr 等價於 uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// 返回一個指向v的原始值的uintptr
// 僅當v的Kind為 Chan, Func, Map, Pointer, Slice, UnsafePointer時,否則會panic
func (v Value) Pointer() uintptr
// 返回一個指向v的原始值的unsafe.Pointer
// 僅當v的Kind為 Chan, Func, Map, Pointer, Slice, UnsafePointer時,否則會panic
func (v Value) UnsafePointer() unsafe.Pointer示例如下
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}輸出
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println會反射獲取參數的類型,如果是reflect.Value類型的話,會自動調用Value.Interface()來獲取其原始值。
換成一個 map 再來一遍
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}輸出
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0設置值
func (v Value) Set(x Value)倘若通過反射來修改反射值,那麼其值必須是可取址的,這時應該通過指針來修改其元素值,而不是直接嘗試修改元素的值。
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// 獲取指針指向的元素
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}輸出如下
114514
11獲取值
func (v Value) Interface() (i any)通過Interface()方法可以獲取反射值原有的值
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}輸出
hello函數
通過反射可以獲取函數的一切信息,也可以反射調用函數
信息
通過反射類型來獲取函數的一切信息
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// 輸出函數名稱,字面量函數的類型沒有名稱
fmt.Println(rType.Name())
// 輸出參數,返回值的數量
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// 輸出第一個參數的類型
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// 輸出第一個返回值的類型
fmt.Println(rResType.Kind())
}輸出
2 1
int
int調用
通過反射值來調用函數
func (v Value) Call(in []Value) []Valuefunc main() {
// 獲取函數的反射值
rType := reflect.ValueOf(Max)
// 傳入參數數組
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}輸出
50結構體
假設有如下結構體
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}訪問字段
reflect.StructField結構的結構如下
type StructField struct {
// 字段名稱
Name string
// 包名
PkgPath string
// 類型名
Type Type
// Tag
Tag StructTag
// 字段的字節偏移
Offset uintptr
// 索引
Index []int
// 是否為嵌套字段
Anonymous bool
}訪問結構體字段的方法有兩種,一種是通過索引來進行訪問,另一種是通過名稱。
type Type interface{
Field(i int) StructField
}通過索引訪問的例子如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 輸出結構體字段的數量
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}輸出
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}通過名稱訪問的例子如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 輸出結構體字段的數量
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}輸出
4
money int false修改字段
倘若要修改結構體字段值,則必須傳入一個結構體指針,下面是一個修改字段的例子
func main() {
// 傳入指針
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// 獲取字段
name := rValue.FieldByName("Name")
// 修改字段值
if (name != reflect.Value{}) { // 如果返回reflect.Value{},則說明該字段不存在
name.SetString("jack")
}
// 輸出結構體
fmt.Println(rValue.Interface())
}輸出
{jack 0 0}對於修改結構體私有字段而言,需要進行一些額外的操作,如下
func main() {
// 傳入指針
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// 獲取一個私有字段
money := rValue.FieldByName("money")
// 修改字段值
if (money != reflect.Value{}) {
// 構造指向該結構體未導出字段的指針反射值
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// 獲取該指針所指向的元素,也就是要修改的字段
field := p.Elem()
// 修改值
field.SetInt(164)
}
// 輸出結構體
fmt.Printf("%+v\n", rValue.Interface())
}訪問 Tag
獲取到StructField後,便可以直接訪問其 Tag
// 如果不存在,ok為false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// 如果不存在,返回空字符串
func (tag StructTag) Get(key string) string示例如下
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}輸出
name true
name訪問方法
訪問方法與訪問字段的過程很相似,只是函數簽名略有區別。reflect.Method結構體如下
type Method struct {
// 方法名
Name string
// 包名
PkgPath string
// 方法類型
Type Type
// 方法對應的函數,第一個參數是接收者
Func Value
// 索引
Index int
}訪問方法信息示例如下
func main() {
// 獲取結構體反射類型
rType := reflect.TypeOf(new(Person)).Elem()
// 輸出方法個數
fmt.Println(rType.NumMethod())
// 遍歷輸出方法信息
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}輸出
1
0 Talk func(main.Person, string) string true如果想要獲取方法的參數和返回值細節,可以通過Method.Func來進行獲取,過程與訪問函數信息一致,將上面的代碼稍微修改下
func main() {
// 獲取結構體反射類型
rType := reflect.TypeOf(new(Person)).Elem()
// 輸出方法個數
fmt.Println(rType.NumMethod())
// 遍歷輸出方法信息
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("方法參數")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("方法返回值")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}可以看到第一個參數是main.Person,也就是接收者類型
1
0 Talk func(main.Person, string) string true
方法參數
main.Person
string
方法返回值
string調用方法
調用方法與調用函數的過程相似,而且並不需要手動傳入接收者,例子如下
func main() {
// 獲取結構體反射類型
rValue := reflect.ValueOf(new(Person)).Elem()
// 輸出方法個數
fmt.Println(rValue.NumMethod())
// 遍歷輸出方法信息
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// 調用方法,並獲取返回值
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// 遍歷輸出返回值
for _, re := range res {
fmt.Println(re.Interface())
}
}
}輸出
1
hello,reflect!創建
通過反射可以構造新的值,reflect包同時根據一些特殊的類型提供了不同的更為方便的函數。
基本類型
// 返回指向反射值的指針反射值
func New(typ Type) Value以string為例
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!結構體
結構體的創建同樣用到reflect.New函數
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// 創建結構體反射值
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}輸出
{ 0 0}切片
反射創建切片
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// 創建切片反射值
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// 遍歷賦值
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}[0 1 2 3 4 5 6 7 8 9]Map
反射創建 Map
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
//構建map反射值
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// 設置值
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]管道
反射創建管道
func MakeChan(typ Type, buffer int) Valuefunc main() {
// 創建管道反射值
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}函數
反射創建函數
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// 傳入包裝類型和函數體
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}輸出
func(int)
1024完全相等
reflect.DeepEqual是反射包下提供的一個用於判斷兩個變量是否完全相等的函數,簽名如下。
func DeepEqual(x, y any) bool該函數對於每一種基礎類型都做了處理,下面是一些類型判斷方式。
- 數組:數組中的每一個元素都完全相等
- 切片:都為
nil時,判為完全相等,或者都不為空時,長度范圍內的元素完全相等 - 結構體:所有字段都完全相等
- 映射表:都為
nil時,為完全相等,都不為nil時,每一個鍵所映射的值都完全相等 - 指針:指向同一個元素或指向的元素完全相等
- 接口:接口的具體類型完全相等時
- 函數:只有兩者都為
nil時才是完全相等,否則就不是完全相等
下面是一些例子:
切片
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}輸出
true結構體
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}輸出
false
false
true