譯自[blog.golang.org/laws-of-reflection]
在計算機中, 反射是程序通過類型,檢測到它自己的結構能力;是一種元編程程;也是一個具大的混淆點
在本文中,我們將通過解釋反射是如何在GO中工作的來澄清它。每個語言的反射模式是不同的。本文着重於GO,
所以后文中的反射都是指GO中的反射
1.類型和接口
因為反射是修建於類型系統之上, 所以讓我們從GO的類型開始講吧。
GO是靜態類型語言。 每個變量都有一個靜態類型。 也就是說, 每一個已經類型在編譯時已經固定了其類型:int, float32...等
假如我們聲明如下:
type MyInt int
var i int
var j MyInt
則i的類型為int; j的類型為MyInt;變量i和j有明顯不同的靜態類型, 而潛在下層類型, 他們可以彼此賦值不須要轉換。
還有一種重要類型是接口類型,它代表了一組固定的方法集。一個接口變量能存儲任一實體值,只要它實現了接口方法集。以大家所
熟知的接口為例, io.Reader和io.Writer, 它們的類型摘自io 包。
// Reader 就是一個包含了基本讀方法的接口
type Reader interface {
Read(p []byte[(n int, err error)
}
// Writher就是一個包含了基本寫方法的接口
type Writer interface{
Write(p []byte)(n int, err error);
}
任何實現了上述讀(寫)方法簽名的類型就說它實現了io.Reader(或者io.Writer).討論這個的目標是, 就是指一個io.Reader類型的變量
可以帶有任何值,只要它的類型中有一個Read方法
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new (bytes.Buffer)
// 等等 ....都可以的...
無論r帶有什么實例值,r的類型總是io.Reader,明白這點是非常重要的;GO是靜態類型,r的靜態類型就是io.Reader。
另一個極其重要的接口例子就是空接口
interface{}
它代表一個空方法集。它可以被任何值滿足。因為任何值都有0到多個方法
有些朋友說,GO的接口是動態類型的, 這就是錯誤引導。 他們是確實靜態類型:一個接口類型的變量總是有一個相同的靜態類型,
雖然,在運行時,一個存儲在一個接口變量上的值可能會改變類型,但值總是滿足接口。
我們需要准確的了解這些,因為反射和接口太相似了。
2. 接口的表達
Russ Cox寫了一篇博客關於GO語言接口值的表達【research.swtch.com/interface】, 我就沒有必須重復這個故事了。簡單總結如下
接口類型的變量存成一個pair(對):值賦給變量和它的描述符descriptor;更准確的說,就是潛在下層的實例值實現了接口,它的類型
描述了下層實例值的完整類型。例如:
var r io.Reader
tty , err := os.OpenFile("/dev/tty",os.O_RDWR, 0)
if er != nil{
return nil, err
}
r = tty
r包含了(值value,類型type)pair對,(tty,*os.File). 注意,*os.File類型實現了遠不只Read方法;雖然接口值提供只有Read方法。
內部值包含所有關於值的類型信息,這就是為什么我們可以如下處理
var w io.Writer
w = r.(io.Writer) // 類型斷言轉換
這在個賦值表達式中是一個類型斷言;它判言的是r里面的東西也實現了io.writer.並且我們能賦值給w, 賦值后,w包含(tty,*os.File)對。
和之前r所包含的對是一樣的的。接口的靜態類型確定了通過接口變量那些方法可以被調用。雖然內部實例值可能有一個更大的方法集。
接着,我們也可以
var empty interface{}
empty = w
我們的空接口值empty,再一次包含了一樣的(tty,*os.File)對。這是很方便的, 空接口可以含有什么信息
一個重要的細節就是, 內部pair對總是有一個(值value,實類型concrete type)並且沒有(value, 接口類型interface type)
現在我們可以談反射了
反射法則
1、反射-從接口值到反射對象
從基本上講, 反射僅是一種用來檢測存儲在接口變量內部(值value,實例類型concret type)pair對的一種機
制。 開始,在反射包中,有兩種類型我們需要了解類型和值。 這兩種類型讓我們可以訪問接口變量內容。
兩個簡單函數分別為reflect.TypeOf和reflect.Valueo,它們從接口變量中取出reflect.Type和
reflect.Value,(也可以從reflect.Value輕松獲得reflect.Type, 但是現在我們還是將Value和Type概念分
開來將)
讓我們首先來講TypeOf
package main
import(
"fmt"
"reflect"
)
func main(){
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x);
}
程序輸出
type:float64
你可能會想這里的接口變量在哪里呀,因為程序看來像是傳遞的一個float64變量x, 不是接口值, 對於
reflect.TypeOf,包含了一空接口:
func TypeOf(i inteface{}) Type
當我們調用reflect.TypeOf(x),首先x被存在一個空接口中,它被當前該接口變量傳入到reflect.TypeOf中;
reflect.TypeOf解包將空接口轉為類型信息
reflect.ValueOf函數,找回值(從這里,我們省略代碼模板,聚焦到執行代碼上)
var x float64 = 3.4
fmt.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讓我們取出里面的值
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
還有方法如SetInt和SetFloat,但是使用它們要理解他們的設置能力,反射的第三條法則,討論如下:
反射庫有一些屬性值得列出來,首先,保持API簡單, 值的getter和setter操作在它可以操作的最大的類型
上:對於所有有符號數為Int64; 例如,值的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, 並不是靜態類型。 假如一個反射對象包含一個用戶定義整形類型的值,如下:
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
v的Kind仍然是reflect.Int, 雖然x的靜態類型是MyInt,而不是int, 換名話說,Kind不區分int和MyInt,雖然Type能。
反射第二法則
2.反射-從反射對象到接口值
就像物理反射一樣,反射在GO生成自己的反像。
給定一個reflect.Value我們使用Interface方法能找回一個接口值;在effect方法打包了類型和值信息到一個接口中並返回結果。
// 返回v的值作為一個接口變量
func (v Value)Interface() interface{}
由此我們可以說
y := v.Inteface().(float64) // y 有類型float64
fmt.Println(y)
。。。。。。。待續