Golang高效實踐之interface、reflection、json實踐


前言

反射是程序校驗自己數據結構和類型的一種機制。文章嘗試解釋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

https://juejin.im/post/5a75a4fb5188257a82110544?utm_campaign=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM