Reflection in Golang

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Static Typed Go

Go作為一門靜態類型的語言,所有變數都定義了其所屬於的類型,不同類型的變數間不能隨意賦值,例如:

1234567
var a intvar b stringa = 1b = "codeb2cc"a = b

a和b不是同一類型的變數,若嘗試直接賦值將會報錯cannot use b (type string) as type int in assignment,不同類型變數間的賦值需要進行類型轉換(Conversion),這點與C/C++裡是一致的。在Go裡,對於變數x與目標類型T,類型轉換需要滿足以下其中一種情況:

  • x可以賦值為類型T的變數
  • x與T有著一致的實作類別型(underlying types)
  • x與T都是匿名指標類型並且具有相同的實作類別型
  • x與T都是整數/浮點數類型
  • x與T都是複數類型
  • x是整數/bytes片段/runes,T是string
  • x是string,T是bytes片段或runes

對於可以進行類型轉換的變數,通過var c = float(100)即可得到目標類型的變數。但在實際開發過程中,我們需要的不僅僅是基本類型的轉換,譬如對於給定的介面類型:

12345
Type I interface {    Read(b Buffer) bool    Write(b Buffer) bool    Close()}

只要實現了這三種方法,就可以認為該類型是合法的、可操作的,但由於無法確定最終傳入變數的類型,我們需要在使用前將其轉化為我們已知的類型。這種情況下由於類型轉化(Conversion)只關心資料,我們需要類型斷言(Type Assertion)來協助我們:

12345
var x Ivar y Filex = y.(I)z, ok := y.(I)

類型斷言判斷變數是否為nil以及是否實現了斷言所需要的介面,若斷言通過將得到一個目標類型的變數,否則將出現run-time panic。若不希望在宣告失敗時程式可以繼續執行,可以通過z, ok := y.(I)的形式判斷變數斷言是否成功,若失敗z為目標類型的零值。

類型斷言應用的常見例子是對JSON資料的解析。Go標準庫中對JSON類與數組的解析返回的結果分別是map[string]interface{}[]interface{},若想正確訪問資料我們就需要對返回結果進行類型斷言:

1234567891011
b := []byte(`{"Foo":"Bar", "Hello": ["W", "o", "r", "l", "d"]}`)var f interface{}err := json.Unmarshal(b, &f)// f類型為interface{}, 需要轉換為map[string]interface{}來訪問資料m := f.(map[string]interface{})fmt.Println(m["Foo"])// 同樣的, m["Hello"]的類型為interface{}, 在正常訪問前我們需要轉換為對應的類型n := m["Hello"].([]byte)

Reflection

通過類型轉換和類型斷言,我們基本能夠解決大多數問題,但單是可以解決是不夠的,我們還需要更加方便(優雅)的解決方案。考慮這樣一個情境,我們定義一個配置結構用來儲存服務的配置資訊,結構中用了多種類型:

12345
type Config struct {  Port int64  Path string  Debug bool}

同時我們將服務的配置儲存在文本中,在服務載入時讀取進來:

Port=8000Path=/tmp/go_reflectionDebug=true

對設定檔解析我們基本可以得到(string, []byte)這樣的元組,我們需要將資料存放區到Config中,由於結構中已經定義了對應的類型,因此我們在儲存時需要進行類型轉換:

1234567891011
data = map[string][]byte{  "Port": []byte("8000"),  "Path": []byte("/tmp/go_reflection"),  "Debug": []byte("true"),}config := Config{}i, err := strconv.ParseInt(string(data["Port"]), 10, 64)if err == nil {  config.Port = i}

對於int64類型我們需要strconv.ParseInt,對於string我們需要string(),對於bool我們需要strconv.ParseBool,在上面這個例子中我們可以通過switch來判斷每個配置項需要進行的轉換,但在實際的應用中配置可能多達數十項,繼續使用這種原始的方法簡直就是一個噩夢。這時我們需要通過Go的Reflect模組來協助我們更優雅地解決這個問題。

反射(Reflection)作為元編程的一種形式,賦予了我們在運行時判斷變數類型的能力。Go的reflect模組通過將資料封裝在reflect.Typereflect.Value中,提供了一系列方法讓我們“動態”地去判斷變數的類型:

12345678
var x = 2013switch reflect.ValueOf(x).Kind() {case reflect.Int:  fmt.Println("It's an int!")case reflect.String:  fmt.Println("It's a string!")}

在我們設定檔的例子中,我們可以根據每個配置的Key動態判斷該進行何種類型轉換並儲存到那個屬性中:

1234567891011121314151617181920
func (s *Config) Set(key string, value []byte) error {    reflectValue := reflect.ValueOf(s).Elem()    reflectField := reflectValue.FieldByName(key)    switch reflectField.Kind() {    case reflect.Int64:        i, err := strconv.ParseUint(string(value), 10, 64)        if err != nil {            return err        }        reflectField.SetInt(i)    case reflect.String:        reflectField.SetString(string(value))    case reflect.Bool        i, err := strconv.ParseBool(string(value))        if err != nil {            return err        }        reflectField.SetBool(i)    }    return nil

這樣,我們只需要通過config.Set("Port", []byte("8000"))一個方法就可以正確地儲存配置資訊,就算結構定義有變更也不需要重寫解析方法。關於反射Golang Blog上有一篇詳細的文章The Laws of Reflection,可以更好地協助我們Go中反射的細節與需要注意的地方,值得一讀。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.