Golang-interface(四 反射)

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

github:https://github.com/ZhangzheBJUT/blog/blob/master/reflect.md 

一 反射的規則

反射是程式執行時檢查其所擁有的結構。尤其是類型的一種能力。這是元編程的一種形式。它同一時候也是造成混淆的重要來源。

每一個語言的反射模型都不同(同一時候很多語言根本不支援反射)。本節將試圖明白解釋在 Go 中的反射是怎樣工作的。

1. 從介面值到反射對象的反射

在主要的層面上。反射僅僅是一個檢查儲存在介面變數中的類型和值的演算法。

在 reflect 包中有兩個類型須要瞭解:Type 和 Value。這兩個類型使得能夠訪問介面變數的內容。還有兩個簡單的函數,reflect.TypeOf 和 reflect.ValueOf,從介面值中分別擷取 reflect.Type 和 reflect.Value。

(註:從 reflect.Value 也非常easy可以獲得 reflect.Type,只是這裡讓 Value 和 Type 在概念上是分離的)

從 TypeOf 開始:

package mainimport (     "fmt"    "reflect")func main() {    var x float64 = 3.4    fmt.Println("type:", reflect.TypeOf(x))}

這個程式列印 type: float64 

介面在哪裡呢,讀者可能會對此有疑慮,看起來程式傳遞了一個 float64 類型的變數 x。而不是一個介面值,到 reflect.TypeOf。可是,它確實就在那裡:如同 godoc 報告的那樣,reflect.TypeOf 的聲明包括了空介面:

// TypeOf 返回 interface{} 中的值反射的類型。

func TypeOf(i interface{}) Type

當調用 reflect.TypeOf(x) 的時候,x 首先儲存於一個作為參數傳遞的空介面中;reflect.TypeOf 解包這個空介面來還原類型資訊。

reflect.ValueOf 函數。當然就是還原那個值(從這裡開始將會略過那些概念示範範例,而聚焦於可啟動並執行代碼):

var x float64 = 3.4fmt.Println("value:", reflect.ValueOf(x))

列印

value: <float64 Value>

除了reflect.Type 和 reflect.Value外,都有很多方法用於檢查和操作它們。一個重要的範例是 Value 有一個 Type 方法返回 reflect.Value 的 Type。還有一個是 Type 和 Value 都有 Kind 方法返回一個常量來表示類型:Uint、Float64、Slice 等等。相同 Value 有叫做 Int 和 Float 的方法能夠擷取儲存在內部的值(跟 int64 和 float64 一樣):

var x float64 = 3.4v := 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                      

同一時候也有類似 SetInt 和 SetFloat 的方法。只是在使用它們之前須要理解可設定性,這部分的主題在以下的第三條軍規中討論。

反射庫有著若干特性值得特別說明。

  • 為了保持 API 的簡潔,“擷取者”和“設定者”用 Value 的最寬泛的類型來處理值:比如,int64 可用於全部帶正負號的整數。也就是說 Value 的 Int 方法返回一個 int64。而 SetInt 值接受一個 int64;所以可能必須轉換到實際的類型:

      var x uint8 = 'x'  v := reflect.ValueOf(x)  fmt.Println("type:", v.Type()) // uint8.  fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.  x = uint8(v.Uint()) // v.Uint 返回一個 uint64.
  • 反射對象的 Kind 描寫敘述了底層類型。而不是靜態類型。假設一個反射對象包括了使用者定義的整數類型的值,就像

      type MyInt int  var x MyInt = 7  v := reflect.ValueOf(x)‘

v 的 Kind 仍然是 reflect.Int。雖然 x 的靜態類型是 MyInt。而不是 int。換句話說,Kind 無法從 MyInt 中區分 int,而 Type 能夠。

2. 從反射對象到介面值的反射

如同物理中的反射,在 Go 中的反射也存在它自己的鏡像。

從 reflect.Value 能夠使用 Interface 方法還原介面值; 此方法能夠高效地打包類型和值資訊到介面表達中,並返回這個結果:

// Interface 以 interface{} 返回 v 的值。func (v Value) Interface() interface{}

能夠這樣作為結果

y := v.Interface().(float64) // y 將為類型 float64。fmt.Println(y)

通過反射對象 v 能夠列印 float64 的表達值。

然而,還能夠做得更好。fmt.Println,fmt.Printf 等其它全部傳遞一個空介面值作為參數的函數。在 fmt 包內部解包的方式就像之前的範例這樣。因此正確的列印 reflect.Value 的內容的方法就是將 Interface 方法的結果進行格式化列印(formatted print routine).

fmt.Println(v.Interface())

為什麼不是 fmt.Println(v)?由於 v 是一個 reflect.Value;這裡希望獲得的是它儲存的實際的值。

因為值是 float64,假設須要的話,甚至能夠使用浮點格式化:

fmt.Printf("value is %7.1e\n", v.Interface())

輸出: 3.4e+00 

再次強調。對於 v.Interface() 無需類型斷言其為 float64。空介面值在內部有實際值的類型資訊。而 Printf 會發現它。 

簡單來說,Interface 方法是 ValueOf 函數的鏡像,除了傳回值總是靜態類型 interface{}。

回想:反射能夠從介面值到反射對象,也能夠反過來。

3. 為了改動反射對象。其值必須可設定

var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) // Error: will panic.

假設執行這個代碼,它報出神奇的 panic 訊息

panic: reflect.Value.SetFloat using unaddressable value

問題不在於值 7.1 不能地址化;在於 v 不可設定。設定性是反射值的一個屬性,並非全部的反射值有此特性。

Value的 CanSet 方法提供了值的設定性;在這個範例中,

var x float64 = 3.4v := reflect.ValueOf(x)fmt.Println("settability of v:" , v.CanSet())

列印

settability of v: false

對不可設定值調用 Set 方法會有錯誤。

可是什麼是設定性? 

設定性有一點點像地址化,可是更嚴格。這是用於建立反射對象的時候。可以改動實際儲存的屬性。

設定性用於決定反射對象是否儲存原始項目。當這樣

var x float64 = 3.4v := reflect.ValueOf(x)

就傳遞了一個 x 的副本到 reflect.ValueOf。所以介面值作為 reflect.ValueOf 參數建立了 x 的副本,而不是 x 本身。因此。假設語句

v.SetFloat(7.1)

同意運行。儘管 v 看起來是從 x 建立的。它也無法更新 x。

反之,假設在反射值內部同意更新 x 的副本。那麼 x 本身不會收到影響。這會造成混淆,而且毫無意義。因此這是非法的,而設定性是用於解決問題的屬性。

這非常奇妙?事實上不是。這實際上是一個常見的非同平常的情況。考慮傳遞 x 到函數:

f(x) 因為傳遞的是 x 的值的副本,而不是 x 本身,所以並不期望 f 能夠改動 x。假設想要 f 直接改動 x,必須向函數傳遞 x 的地址(也就是。指向 x 的指標):

f(&x) 這是清晰且熟悉的,而反射通過相同的途徑工作。假設希望通過反射來改動 x,必須向反射庫提供一個希望改動的值的指標。

來試試吧。

首先像尋常那樣初始化 x,然後建立指向它的反射值,叫做 p。

var x float64 = 3.4p := reflect.ValueOf(&x) // 注意:擷取 X 的地址。

fmt.Println("type of p:", p.Type())fmt.Println("settability of p:" , p.CanSet())

這樣輸出為

type of p: *float64settability of p: false

反射對象 p 並非可設定的。並且我們也不希望設定 p,實際上是 *p。為了獲得 p 指向的內容,調用值上的 Elem 方法,從指標間接指向,然後儲存反射值的結果叫做 v:

v := p.Elem()fmt.Println("settability of v:" , v.CanSet())

如今 v 是可設定的反射對象,如同示範範例的輸出,

settability of v: true

而因為它來自 x,終於能夠使用 v.SetFloat 來改動 x 的值:

v.SetFloat(7.1)fmt.Println(v.Interface())fmt.Println(x)

得到期望的輸出

7.17.1

反射可能非常難理解,可是語言做了它應該做的,雖然底層的實現被反射的 Type 和 Value 隱藏了。務必記得反射值須要某些內容的地址來改動它指向的東西。

二結構體

在之前的範例中 v 本身不是指標,它僅僅是從一個指標中擷取的。這樣的情況更加常見的是當使用反射改動結構體的欄位的時候。也就是當有結構體的地址的時候。能夠改動它的欄位。

這裡有一個分析結構值 t 的簡單範例。

因為希望對結構體進行改動,所以從它的地址建立了反射對象。設定了 typeOfT 為其類型。然後用直白的方法調用來遍曆其欄位(參考 reflect 包瞭解很多其它資訊)。注意從結構類型中解析了欄位名字,可是欄位本身是原始的 reflect.Value 對象。

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 = 231: B string = skidoo

另一個關於設定性的要點:T 的欄位名要大寫(可匯出),由於僅僅有可匯出的欄位是可設定的。

因為 s 包括可設定的反射對象,所以能夠改動結構體的欄位。

s.Field(0).SetInt(77)s.Field(1).SetString("Sunset Strip")fmt.Println("t is now", t)

這裡是結果:

t is now {77 Sunset Strip}

假設改動程式使得 s 建立於 t,而不是 &t,調用 SetInt 和 SetString 會失敗,由於 t 的欄位不可設定。

三 總結

反射的規則例如以下: 

從介面值到反射對象的反射 

從反射對象到介面值的反射 

為了改動反射對象,其值必須可設定 

一旦理解了 Go 中的反射的這些規則,就會變得easy使用了,儘管它仍然非常微妙。這是一個強大的工具,除非真得有必要,否則應當避免使用或小心使用。

還有大量的關於反射的內容沒有涉及到——channel 上的發送和接收、分配記憶體、使用 slice 和 map、調用方法和函數。

案例代碼: https://github.com/ZhangzheBJUT/GoProject/blob/master/reflect/main.go

參考:  http://blog.golang.org/laws-of-reflection

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.