前言
反射是程式校驗自己資料結構和型別的一種機制。文章嘗試解釋Golang的反射機制工作原理,每種程式語言的反射模型都是不同的,有很多語言甚至都不支援反射。
Interface
在將反射之前需要先介紹下介面interface,因為Golang的反射實現是基於interface的。Golang是靜態型別語言,每個變數擁有一個靜態型別,在編譯器就已經確定,例如int,float32,*MyType, []byte等等。如果我們定義:
type MyInt int var i int var j MyInt
int型別的I和MyInt型別的j是不同型別的變數,在沒有限制型別轉換的情況下它們不能相互賦值,即便它們的底層型別是一樣的。
介面interface型別是最重要的一種資料型別,代表的一些方法的集合。interface變數可以儲存任意的資料型別,只要該資料型別實現了interface的方法集合。例如io包的io.Reader和io.Writer:
// Reader is the interface that wraps the basic Read method. type Reader interface { Read(p []byte) (n int, err error) } // Writer is the interface that wraps the basic Write method. type Writer interface { Write(p []byte) (n int, err error) }
任意實現了Read方法的型別都是Reader型別,也就是說可以賦值給Reader介面,換句話說就是Reader interface可以儲存任意的實現了Read方法的型別:
var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) // and so on
需要明確的是無論上述變數r實際儲存的是什麼型別,r的型別永遠都是io.Reader,這就是為什麼說Golang是靜態型別程式語言,因為r宣告時是io.Reader,在編譯期就已經明確了型別。
Interface一個特別重要的示例是空介面:
interface{}
它代表一個空的方法集合,因為任意型別值都有0個多少多個方法,所以空的介面interface{}可以儲存任意型別值。
有些人說Golang的interface是動態型別,其實是種誤解。介面是靜態型別,interface變數定義時就宣告瞭一種靜態型別,即便interface儲存的值在執行時會改變型別,但是interface的型別是一定的。
一個interface型別變數會儲存一對資料,具體型別的值和值的具體型別(value, concrete type)。例如:
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
上述的interface變數I會儲存一對資料(tty,*os.File)。需要注意的是*os.File型別不止單單實現了Read方法,還實現了其他方法,比如Write方法。即便interface型別變數i值提供了訪問Read的方法,i還是攜帶了*os.File變數的所有型別資訊。所以可以將i轉換為io.Writer型別:
var w io.Writer w = r.(io.Writer)
上述的表示式是一個型別斷言,斷言r也實現了io.Writer,所以可以賦值給w,否則會panic。完成賦值後,w會攜帶一對值(tty,*os.File),和r一樣的一對值。介面的靜態型別決定了上述的tty能夠呼叫的方法,即便它實際上包含了更多的方法。
也可以將它賦值給空介面:
var empty interface{} empty = w
空介面empty也攜帶同樣的對值(tty,*os.File)。因為任意的型別都是空介面所以不用轉換。
反射reflection
從本質上講,反射是校驗介面儲存(value,concrete type)值對的一種機制。分別對應的reflect包的Value和Type型別。通過Value和Type型別可以訪問到interface變數的儲存內容,reflect.TypeOf和reflect.ValueOf將會返回interface變數的reflect.Type和reflect.Value型別值。
從TypeOf開始:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) }
結果將會輸出:
type: float64
你可能會有疑問,反射是基於interface,那麼這裡的interface在哪兒呢?這就需要了解TypeOf的定義:
// TypeOf returns the reflection Type of the value in the interface{}. func TypeOf(i interface{}) Type
也就是說TypeOf會用interface{}把引數儲存起來,然後reflect.TypeOf再從interface{}中獲取資訊。
同理ValueOf的函式定義為:
// 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
示例:
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
結果輸出:
type: float64 kind is float64: true value: 3.4
所以我們可以得出反射的第一條規則是:反射物件是從介面值獲取的。
規則2:可以從反射物件中獲取介面值。
利用reflect.Value的Interface方法可以獲得傳遞過來的空介面interface{}:
// Interface returns v's value as an interface{}. func (v Value) Interface() interface{}
示例:
y := v.Interface().(float64) // y will have type float64. fmt.Println(y)
規則3:通過反射物件的set方法可以修改實際儲存的變數,前提是儲存的變數是可以被修改的。
反射定義變數是可以被修改的(settable)條件是傳遞變數的指標,因為如果是值傳遞的話,反射物件set方法改變的是一份拷貝,所以會顯得怪異而且沒有意義,所以乾脆就將值傳遞的情況定義為不可修改的,如果嘗試修改就會觸發panic。
示例:
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic
報錯如下:
panic: reflect.Value.SetFloat using unaddressable value
可以通過反射物件Value的CanSet方法判斷是否是可修改的:
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet())
輸出:
settability of v: false
可被修改的情況:
var x float64 = 3.4 p := reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of p:", p.Type()) fmt.Println("settability of p:", p.CanSet())
輸出:
type of p: *float64 settability of p: false
反射物件p是不可被修改的,因為p不是我們想要修改的,*p才是。呼叫Value的Elem方法可以獲取p指向的內容,並且內容儲存在Value物件中:
v := p.Elem() fmt.Println("settability of v:", v.CanSet())
輸出:
settability of v: true
示例:
v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)
輸出:
7.1 7.1
結構體
只要有結構體的地址我們就可以用反射修改結構體的內容。下面是個簡單的示例:
type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) }
程式輸出:
0: A int = 23 1: B string = skidoo
修改:
s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)
程式輸出:
t is now {77 Sunset Strip}
所以反射的三條規則總結如下:
規則1:反射物件是從介面值獲取的。
規則2:可以從反射物件中獲取介面值。
規則3:通過反射物件的set方法可以修改實際儲存的settable變數
Json
由於Json的序列化(編碼)和反序列化(解碼)都會用到反射,所以這裡放在一起講解。
Json編碼
可以用Marshal函式完成Json編碼:
func Marshal(v interface{}) ([]byte, error)
給定一個Golang的結構體Message:
type Message struct { Name string Body string Time int64 }
Message的例項m為:
m := Message{"Alice", "Hello", 1294706395881547000}
Marshal編碼Json:
b, err := json.Marshal(m)
如果工作正常,err為nil,b為[]byte型別的Json字串:
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
Json編碼規則:
1.Json物件只支援string作為key;所以想要編碼Golang map型別必須是map[stirng]T,其中T表示Golang支援的任意型別。
2.Channel,complex和函式型別不能被編碼
3.迴圈引用巢狀的結構體不支援,他們會造成Marshal進入一個未知的迴圈體重
4.指標將會被編碼指向的內容本身,如果指標是nil將會是null
Json解碼
可以用Unmarshal解碼Json資料:
func Unmarshal(data []byte, v interface{}) error
首先我們必須要先建立解碼資料儲存的變數:
var m Message
然後傳遞變數的指標(參考反射規則3):
err := json.Unmarshal(b, &m)
如果b包含可用的Json並且適合m,那麼err將會是nil,b的資料會被儲存在m中,就好像下面的賦值一樣:
m = Message{ Name: "Alice", Body: "Hello", Time: 1294706395881547000, }
Unmarshal是怎麼識別要儲存的解碼欄位的呢?例如Json的一個Key為”Foo”,Unmarshal會找根據下面的規則順序匹配:
1.找名為“Foo”的欄位tag
2.找名為“Foo”,”FOO”或者“FoO”的欄位名稱
再看下面的Json資料解碼會匹配到Golang的什麼資料型別呢:
b := []byte(`{"Name":"Bob","Food":"Pickle"}`) var m Message err := json.Unmarshal(b, &m)
Unmarshal只會解碼它認識的欄位。在這個例子中,只有Name欄位出現在m中,所以Food欄位會被忽略。當你想在一個大的Json資料中提取你要想的部分欄位時,該特性是非常有用的。這意味著你不需要關心Json的所有欄位,只需要關心你要用到的欄位即可。
json包會用map[string]interface{}儲存Json物件,用[]interface{}儲存陣列。當Unmarshal Json物件作為interface{}值時,預設Golang的concrete type為:
Json booleans型別預設為bool
Json 數字預設為float64
Json strings預設為string
Json null預設為nil
示例:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) var f interface{} err := json.Unmarshal(b, &f)
相對於下面的賦值操作:
f = map[string]interface{}{ "Name": "Wednesday", "Age": 6, "Parents": []interface{}{ "Gomez", "Morticia", }, }
如果想要訪問f的底層map[string]interface{}資料結構需要斷言:
m := f.(map[string]interface{})
然後遍歷map接著訪問其他成員:
for k, v := range m { switch vv := v.(type) { case string: fmt.Println(k, "is string", vv) case float64: fmt.Println(k, "is float64", vv) case []interface{}: fmt.Println(k, "is an array:") for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, "is of a type I don't know how to handle") } }
上述示例中,可以定義一個結構體來儲存:
type FamilyMember struct { Name string Age int Parents []string } var m FamilyMember err := json.Unmarshal(b, &m)
Unmarshal資料進入FamilyMembear值時,會自動給nil 切片分配記憶體,同理如果有指標,map也會自動分配記憶體。
總結
文章介紹了interface、reflection、json,其中reflection是基於interface實現的,而json的編碼和解碼用到了reflection。
參考
https://blog.golang.org/json-and-go
https://blog.golang.org/laws-of-reflection