1. 程式人生 > >【Go】Go語言中反射包的實現原理(The Laws of Reflection)

【Go】Go語言中反射包的實現原理(The Laws of Reflection)

轉載自 http://studygolang.com/articles/2157  作者:kjfcpua

前言

過去只是知道某些語言帶有反射,但是一直沒機會使用這種高階功能,所以也沒有深入瞭解過。昨天看golang時裡面提到reflection,既然這麼多語言支援這個性質,那就深入瞭解下好了。這篇文件翻譯自官方文件的The Laws of Reflection,翻譯目的不是為了翻譯,而是加深自己記憶以及理解,所以有些地方可能不會直譯,因為我沒那麼高水平,有時自己能看懂,但是按著原話翻譯出來給別人聽感覺好難。某些專用名詞會繼續保留原文,有時,其實我覺得還是英文更加容易理解。

The Laws of Reflection翻譯

計算機中提到的反射指的是程式藉助某種手段檢查自己結構的一種能力,通常就是藉助程式語言中定義的各種型別(types)。同時反射也是困惑的最大來源之一。

在這篇文章中,我們嘗試通過解釋反射在Go中是如何工作來掃除這些困惑。不同語言的反射模型(reflection model)的實現也是不同的(當然某些語言根本就不支援反射),但是這篇文章是關於Go的,所以這篇文章剩餘部分提到“反射”的時候特指“Go中的反射”。

型別和介面(Types and interfaces)

因為反射是建立在型別系統(the type system)上的,所以讓我們從複習Go中的型別開始講起。

Go是靜態型別化的。每個變數都有一個靜態型別,也就是說,在編譯的時候變數的型別就被很精確地確定下來了,比如要麼是int,或者是float32,或者是MyType型別,或者是[]byte等等。如果我們像下面這樣宣告:

1
2
3
4
type MyInt int
var i int
var j MyInt

那麼i的型別就是int,而j的型別就是MyInt。這裡的變數i和j具有不同的靜態型別,雖然它們有相同的底層型別(underlying type),如果不顯示的進行強制型別轉換它們是不能互相賦值的。

型別(type)中非常重要的一類(category)就是介面型別(interface type),一個介面就表示一組確定的方法(method)集合。一個介面變數能儲存任意的具體值(這裡的具體concrete就是指非介面的non-interface),只要這個具體值所屬的型別實現了這個介面的所有方法。一個大家都很熟悉的例子是io.Reader和io.Writer,型別Reader和型別Writer來自

io包:

1
2
3
4
5
6
7
8
9
// 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方法(或者Write方法)的任意型別都可以說它實現了io.Reader介面(或者io.Writer介面)。這就意味著io.Reader介面變數能夠儲存任意實現了Read方法的型別所定義的值,比如:

1
2
3
4
5
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

明確r到底儲存了什麼樣的具體值非常重要,但是這裡r的型別卻總是io.Reader:注意Go是靜態型別化的,而r的靜態型別是io.Reader。

一個非常非常重要的介面型別例子就是空介面:

1
interface{}

空介面表示方法集合為空並且可以儲存任意值,因為任意值都有0個或者更多方法。

有些人說Go的介面是動態型別化的,但這是一種誤導。Go的介面都是靜態型別化的:一個介面型別變數總是保持同一個靜態型別,即使在執行時它儲存的值的型別發生變化,這些值總是滿足這個介面。

我們需要搞清楚上面說的這些,因為反射和介面是緊緊聯絡在一起的。

介面的表示(The representation of an interface)

Russ Cox曾經寫過一篇關於Go中介面值的表示的部落格detailed blog post。在這裡我們沒必要重複他的整篇文章內容了,但是簡單概括下還是應該的。

一個介面型別變數儲存了一個pair:賦值給這個介面變數的具體值,以及這個值的型別描述符。更進一步的講,這個”值”是實現了這個介面的底層具體資料項(underlying concrete data item),而這個“型別”是描述了那個項(item)的全型別(full type)。舉個例子,執行完下面這些:

1
2
3
4
5
6
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
  return nil, err
}
r = tty

示意性的講,此時r就包含了(value, type)對,即(tty, os.File)。注意,除了Read方法以外,型別os.File也實現了其它方法;即使這個介面值僅僅提供了對Read方法的訪問,這個介面值內部仍然帶有關於這個值的全部型別資訊。這就是為什麼我們能幹下面這些事兒:

1
2
var w io.Writer
w = r.(io.Writer)

這個賦值操作中的表示式是一個型別斷言(type assertion);它所斷言的是r中儲存的項(item)也實現了io.Writer介面,所以我們可以把它賦值給w。賦值操作完畢以後,w將會包含 (tty, *os.File)對。這個pair跟r中的pair是同樣的。介面的靜態型別決定了能用介面變數呼叫哪些方法,即使接口裡存的具體值內部可能還有一大坨其它方法。(換句話說,介面定義的方法集合是該種介面變數所儲存的具體值所含有的方法集合的一個子集,通過這個介面變數只能呼叫這個介面定義過的方法,沒法通過這個介面變數呼叫其它任何方法。)

繼續說,我們可以這樣做:

1
2
var empty interface{}
empty = w

我們的空介面值,empty,也能包含同樣的pair即(tty, *os.File)。這樣的話就很方便了,一個空介面可以儲存任意值和我們所需要的關於所儲存值的全部資訊。

(我們不需要在這裡做型別斷言了,因為可以靜態地知道w滿足空介面。在上面那個把一個值從一個Reader移到一個Writer的例子裡,我們需要顯式地用一個型別斷言,因為Writer定義的方法集合不是Reader定義的方法集合的子集。)

一個很重要的細節是,一個介面中的pair總有(值,具體型別)這樣的格式,而不能有(值,介面型別)這樣的格式。介面不能儲存介面值(也就是說,你沒法把一個介面變數值儲存到一個介面變數中,只能把一個具體型別的值儲存到一個介面變數中。)

現在,我們終於準備好了可以看看反射是怎麼回事兒了。

第一反射定律(The first law of reflection)

1.從介面值到反射物件的反射(Reflection goes from interface value to reflection object)

最最基本的,反射是一種檢查儲存在介面變數中的(型別,值)對的機制。作為一個開始,我們需要知道reflect包中的兩個型別:TypeValue。這兩種型別給了我們訪問一個介面變數中所包含的內容的途徑,另外兩個簡單的函式reflect.Typeof和reflect.Valueof可以檢索一個介面值的reflect.Type和reflect.Value部分。(還有就是,我們可以很容易地從reflect.Value到達reflect.Type,但是現在暫且讓我們先把Value和Type的概念分開說。先劇透,從Value到達Type是通過Value中定義的某些方法來實現的,雖然先分開講,但是後面多注意一下。)

讓我們從Typeof開始:

1
2
3
4
5
6
7
8
9
10
11