反射是程序校驗本身數據結構和類型的一種機制。文章嘗試解釋Golang的反射機制工做原理,每種編程語言的反射模型都是不一樣的,有不少語言甚至都不支持反射。golang
在將反射以前須要先介紹下接口interface,由於Golang的反射實現是基於interface的。Golang是靜態類型語言,每一個變量擁有一個靜態類型,在編譯器就已經肯定,例如int,float32,*MyType, []byte等等。若是咱們定義:編程
type MyInt int var i int var j MyInt
int類型的I和MyInt類型的j是不一樣類型的變量,在沒有限制類型轉換的狀況下它們不能相互賦值,即使它們的底層類型是同樣的。json
接口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{}能夠存儲任意類型值。post
有些人說Golang的interface是動態類型,實際上是種誤解。接口是靜態類型,interface變量定義時就聲明瞭一種靜態類型,即使interface存儲的值在運行時會改變類型,可是interface的類型是必定的。編碼
一個interface類型變量會存儲一對數據,具體類型的值和值的具體類型(value, concrete type)。例如:spa
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)。由於任意的類型都是空接口因此不用轉換。
從本質上講,反射是校驗接口存儲(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的序列化(編碼)和反序列化(解碼)都會用到反射,因此這裏放在一塊兒講解。
能夠用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
能夠用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