
Hi,大家好,我是明哥,
在自己學習 Golang 的這段時間里,我寫了詳細的學習筆記放在我的個人微信公眾號 《Go編程時光》,對于 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長,
我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime
當我在使用 Python 的時候,我甚至可以做到不需要知道什么是內省,什么是反射,就可以立即使用內省去做一些事情,
而在學習 Go 語言后,反射在我這卻變成了一個難點,一直感覺這個 反射物件 的概念例外的抽象,
這篇文章還是會跟上篇文章一樣,盡量使用圖解來解釋一些抽象的概念,如果是我理解有誤,還希望你在文章尾部給我留言指正,謝謝,
關于反射的內容,我分為了好幾篇,這一篇是入門篇,會從經典的反射三大定律入手,寫一些 demo 代碼,告訴你反射的基本內容,
1. 真實世界與反射世界
在本篇文章里,為了區分反射前后的變數值型別,我將反射前環境稱為 真實世界,而將反射后的環境稱為 反射世界,這種比喻不嚴謹,但是對于我理解是有幫助的,也希望對你有用,
在反射的世界里,我們擁有了獲取一個物件的型別,屬性及方法的能力,

2. 兩種型別:Type 和 Value
在 Go 反射的世界里,有兩種型別非常重要,是整個反射的核心,在學習 reflect 包的使用時,先得學習下這兩種型別:
- reflect.Type
- reflect.Value
它們分別對應著真實世界里的 type 和 value,只不過在反射物件里,它們擁有更多的內容,
從原始碼上來看,reflect.Type 是以一個介面的形式存在的
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
In(i int) Type
Key() Type
Len() int
NumField() int
NumIn() int
NumOut() int
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
而 reflect.Value 是以一個結構體的形式存在,
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
同時它接收了很多的方法(見下表),這里出于篇幅的限制這里也沒辦法一一介紹,
Addr
Bool
Bytes
runes
CanAddr
CanSet
Call
CallSlice
call
Cap
Close
Complex
Elem
Field
FieldByIndex
FieldByName
FieldByNameFunc
Float
Index
Int
CanInterface
Interface
InterfaceData
IsNil
IsValid
IsZero
Kind
Len
MapIndex
MapKeys
MapRange
Method
NumMethod
MethodByName
NumField
OverflowComplex
OverflowFloat
OverflowInt
OverflowUint
Pointer
Recv
recv
Send
send
Set
SetBool
SetBytes
setRunes
SetComplex
SetFloat
SetInt
SetLen
SetCap
SetMapIndex
SetUint
SetPointer
SetString
Slice
Slice3
String
TryRecv
TrySend
Type
Uint
UnsafeAddr
assignTo
Convert
通過上一節的內容(),我們知道了一個介面變數,實際上都是由一 pair 對(type 和 data)組合而成,pair 對中記錄著實際變數的值和型別,也就是說在真實世界里,type 和 value 是合并在一起組成 介面變數的,
而在反射的世界里,type 和 data 卻是分開的,他們分別由 reflect.Type 和 reflect.Value 來表現,
3. 解讀反射的三大定律
Go 語言里有個反射三定律,是你在學習反射時,很重要的參考:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
翻譯一下,就是:
- 反射可以將介面型別變數 轉換為“反射型別物件”;
- 反射可以將 “反射型別物件”轉換為 介面型別變數;
- 如果要修改 “反射型別物件” 其型別必須是 可寫的;
第一定律
Reflection goes from interface value to reflection object.
為了實作從介面變數到反射物件的轉換,需要提到 reflect 包里很重要的兩個方法:
- reflect.TypeOf(i) :獲得介面值的型別
- reflect.ValueOf(i):獲得介面值的值
這兩個方法回傳的物件,我們稱之為反射物件:Type object 和 Value object,

舉個例子,看下這兩個方法是如何使用的?
package main
import (
"fmt"
"reflect"
)
func main() {
var age interface{} = 25
fmt.Printf("原始介面變數的型別為 %T,值為 %v \n", age, age)
t := reflect.TypeOf(age)
v := reflect.ValueOf(age)
// 從介面變數到反射物件
fmt.Printf("從介面變數到反射物件:Type物件的型別為 %T \n", t)
fmt.Printf("從介面變數到反射物件:Value物件的型別為 %T \n", v)
}
輸出如下
原始介面變數的型別為 int,值為 25
從介面變數到反射物件:Type物件的型別為 *reflect.rtype
從介面變數到反射物件:Value物件的型別為 reflect.Value
如此我們完成了從介面型別變數到反射物件的轉換,
等等,上面我們定義的 age 不是 int 型別的嗎?第一法則里怎么會說是介面型別的呢?
關于這點,其實在上一節(關于介面的三個 『潛規則』)已經提到過了,由于 TypeOf 和 ValueOf 兩個函式接收的是 interface{} 空介面型別,而 Go 語言函式都是值傳遞,因此Go語言會將我們的型別隱式地轉換成介面型別,
// TypeOf returns the reflection Type of the value in the interface{}.TypeOf returns nil.
func TypeOf(i interface{}) Type
// ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value
第二定律
Reflection goes from reflection object to interface value.
和第一定律剛好相反,第二定律描述的是,從反射物件到介面變數的轉換,

通過原始碼可知, reflect.Value 的結構體會接收 Interface 方法,回傳了一個 interface{} 型別的變數(注意:只有 Value 才能逆向轉換,而 Type 則不行,這也很容易理解,如果 Type 能逆向,那么逆向成什么呢?)
// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}
這個函式就是我們用來實作將反射物件轉換成介面變數的一個橋梁,
例子如下
package main
import (
"fmt"
"reflect"
)
func main() {
var age interface{} = 25
fmt.Printf("原始介面變數的型別為 %T,值為 %v \n", age, age)
t := reflect.TypeOf(age)
v := reflect.ValueOf(age)
// 從介面變數到反射物件
fmt.Printf("從介面變數到反射物件:Type物件的型別為 %T \n", t)
fmt.Printf("從介面變數到反射物件:Value物件的型別為 %T \n", v)
// 從反射物件到介面變數
i := v.Interface()
fmt.Printf("從反射物件到介面變數:新物件的型別為 %T 值為 %v \n", i, i)
}
輸出如下
原始介面變數的型別為 int,值為 25
從介面變數到反射物件:Type物件的型別為 *reflect.rtype
從介面變數到反射物件:Value物件的型別為 reflect.Value
從反射物件到介面變數:新物件的型別為 int 值為 25
當然了,最后轉換后的物件,靜態型別為 interface{} ,如果要轉成最初的原始型別,需要再型別斷言轉換一下,關于這點,我已經在上一節里講解過了,你可以點此前往復習:(),
i := v.Interface().(int)
至此,我們已經學習了反射的兩大定律,對這兩個定律的理解,我畫了一張圖,你可以用下面這張圖來加強理解,方便記憶,

第三定律
To modify a reflection object, the value must be settable.
反射世界是真實世界的一個『映射』,是我的一個描述,但這并不嚴格,因為并不是你在反射世界里所做的事情都會還原到真實世界里,
第三定律引出了一個 settable (可設定性,或可寫性)的概念,
其實早在以前的文章中,我們就一直在說,Go 語言里的函式都是值傳遞,只要你傳遞的不是變數的指標,你在函式內部對變數的修改是不會影響到原始的變數的,
回到反射上來,當你使用 reflect.Typeof 和 reflect.Valueof 的時候,如果傳遞的不是介面變數的指標,反射世界里的變數值始終將只是真實世界里的一個拷貝,你對該反射物件進行修改,并不能反映到真實世界里,
因此在反射的規則里
- 不是接收變數指標創建的反射物件,是不具備『可寫性』的
- 是否具備『可寫性』,可使用
CanSet()來獲取得知 - 對不具備『可寫性』的物件進行修改,是沒有意義的,也認為是不合法的,因此會報錯,
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "Go編程時光"
v := reflect.ValueOf(name)
fmt.Println("可寫性為:", v.CanSet())
}
輸出如下
可寫性為: false
要讓反射物件具備可寫性,需要注意兩點
- 創建反射物件時傳入變數的指標
- 使用
Elem()函式回傳指標指向的資料
完整代碼如下
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "Go編程時光"
v1 := reflect.ValueOf(&name)
fmt.Println("v1 可寫性為:", v1.CanSet())
v2 := v1.Elem()
fmt.Println("v2 可寫性為:", v2.CanSet())
}
輸出如下
v1 可寫性為: false
v2 可寫性為: true
知道了如何使反射的世界里的物件具有可寫性后,接下來是時候了解一下如何對修改更新它,
反射物件,都會有如下幾個以 Set 單詞開頭的方法

這些方法就是我們修改值的入口,
來舉個例子
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "Go編程時光"
fmt.Println("真實世界里 name 的原始值為:", name)
v1 := reflect.ValueOf(&name)
v2 := v1.Elem()
v2.SetString("Python編程時光")
fmt.Println("通過反射物件進行更新后,真實世界里 name 變為:", name)
}
輸出如下
真實世界里 name 的原始值為: Go編程時光
通過反射物件進行更新后,真實世界里 name 變為: Python編程時光
參考文章
- 思否:Go 語言反射三定律

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/3637.html
標籤:Go
上一篇:C 實戰練習題目80
