新聞中心
反射是眾多編程語(yǔ)言中的一個(gè)非常實(shí)用的功能,它是一種能夠自描述、自控制的應(yīng)用,Go語(yǔ)言也對(duì)反射提供了友好的支持。

Go語(yǔ)言中使用反射可以在編譯時(shí)不知道類(lèi)型的情況下更新變量,在運(yùn)行時(shí)查看值、調(diào)用方法以及直接對(duì)他們的布局進(jìn)行操作。
由于反射是建立在類(lèi)型系統(tǒng)(type system)上的,所以我們先來(lái)復(fù)習(xí)一下Go語(yǔ)言中的類(lèi)型。
Go語(yǔ)言中的類(lèi)型
Go語(yǔ)言是一門(mén)靜態(tài)類(lèi)型的語(yǔ)言,每個(gè)變量都有一個(gè)靜態(tài)類(lèi)型,類(lèi)型在編譯的時(shí)候確定下來(lái)。
type MyInt int
var i int
var j MyInt
變量 i 的類(lèi)型是 int,變量 j 的類(lèi)型是 MyInt,雖然它們有著相同的基本類(lèi)型,但靜態(tài)類(lèi)型卻不一樣,在沒(méi)有類(lèi)型轉(zhuǎn)換的情況下,它們之間無(wú)法互相賦值。
接口是一個(gè)重要的類(lèi)型,它意味著一個(gè)確定的方法集合,一個(gè)接口變量可以存儲(chǔ)任何實(shí)現(xiàn)了接口的方法的具體值(除了接口本身),例如 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)
}
如果一個(gè)類(lèi)型聲明實(shí)現(xiàn)了 Reader(或 Writer)方法,那么它便實(shí)現(xiàn)了 io.Reader(或 io.Writer),這意味著一個(gè) io.Reader 的變量可以持有任何一個(gè)實(shí)現(xiàn)了 Read 方法的的類(lèi)型的值。
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
必須要弄清楚的一點(diǎn)是,不管變量 r 中的具體值是什么,r 的類(lèi)型永遠(yuǎn)是 io.Reader,由于Go語(yǔ)言是靜態(tài)類(lèi)型的,r 的靜態(tài)類(lèi)型就是 io.Reader。
在接口類(lèi)型中有一個(gè)極為重要的例子——空接口:
interface{}
它表示了一個(gè)空的方法集,一切值都可以滿(mǎn)足它,因?yàn)樗鼈兌加辛阒祷蚍椒ā?/p>
有人說(shuō)Go語(yǔ)言的接口是動(dòng)態(tài)類(lèi)型,這是錯(cuò)誤的,它們都是靜態(tài)類(lèi)型,雖然在運(yùn)行時(shí)中,接口變量存儲(chǔ)的值也許會(huì)變,但接口變量的類(lèi)型是不會(huì)變的。我們必須精確地了解這些,因?yàn)榉瓷渑c接口是密切相關(guān)的。
關(guān)于接口我們就介紹到這里,下面我們看看Go語(yǔ)言的反射三定律。
反射第一定律:反射可以將“接口類(lèi)型變量”轉(zhuǎn)換為“反射類(lèi)型對(duì)象”
注:這里反射類(lèi)型指 reflect.Type 和 reflect.Value。
從使用方法上來(lái)講,反射提供了一種機(jī)制,允許程序在運(yùn)行時(shí)檢查接口變量?jī)?nèi)部存儲(chǔ)的 (value, type) 對(duì)。
在最開(kāi)始,我們先了解下 reflect 包的兩種類(lèi)型 Type 和 Value,這兩種類(lèi)型使訪(fǎng)問(wèn)接口內(nèi)的數(shù)據(jù)成為可能,它們對(duì)應(yīng)兩個(gè)簡(jiǎn)單的方法,分別是 reflect.TypeOf 和 reflect.ValueOf,分別用來(lái)讀取接口變量的 reflect.Type 和 reflect.Value 部分。
當(dāng)然,從 reflect.Value 也很容易獲取到 reflect.Type,目前我們先將它們分開(kāi)。
首先,我們下看 reflect.TypeOf:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}運(yùn)行結(jié)果如下:
type: float64
大家可能會(huì)疑惑,為什么沒(méi)看到接口?這段代碼看起來(lái)只是把一個(gè) float64 類(lèi)型的變量 x 傳遞給 reflect.TypeOf 并沒(méi)有傳遞接口。其實(shí)在 reflect.TypeOf 的函數(shù)簽名里包含一個(gè)空接口:
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
我們調(diào)用 reflect.TypeOf(x) 時(shí),x 被存儲(chǔ)在一個(gè)空接口變量中被傳遞過(guò)去,然后 reflect.TypeOf 對(duì)空接口變量進(jìn)行拆解,恢復(fù)其類(lèi)型信息。
函數(shù) reflect.ValueOf 也會(huì)對(duì)底層的值進(jìn)行恢復(fù):
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))
}運(yùn)行結(jié)果如下:
value: 3.4
類(lèi)型 reflect.Type 和 reflect.Value 都有很多方法,我們可以檢查和使用它們,這里我們舉幾個(gè)例子。
類(lèi)型 reflect.Value 有一個(gè)方法 Type(),它會(huì)返回一個(gè) reflect.Type 類(lèi)型的對(duì)象。
Type 和 Value 都有一個(gè)名為 Kind 的方法,它會(huì)返回一個(gè)常量,表示底層數(shù)據(jù)的類(lèi)型,常見(jiàn)值有:Uint、Float64、Slice 等。
Value 類(lèi)型也有一些類(lèi)似于 Int、Float 的方法,用來(lái)提取底層的數(shù)據(jù):
- Int 方法用來(lái)提取 int64
- Float 方法用來(lái)提取 float64,示例代碼如下:
package main
import (
"fmt"
"reflect"
)
func main() {
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())
}運(yùn)行結(jié)果如下:
type: float64
kind is float64: true
value: 3.4
還有一些用來(lái)修改數(shù)據(jù)的方法,比如 SetInt、SetFloat。在介紹它們之前,我們要先理解“可修改性”(settability),這一特性會(huì)在下面進(jìn)行詳細(xì)說(shuō)明。
反射庫(kù)提供了很多值得列出來(lái)單獨(dú)討論的屬性,下面就來(lái)介紹一下。
首先是介紹下 Value 的 getter 和 setter 方法,為了保證 API 的精簡(jiǎn),這兩個(gè)方法操作的是某一組類(lèi)型范圍最大的那個(gè)。比如,處理任何含符號(hào)整型數(shù),都使用 int64,也就是說(shuō) Value 類(lèi)型的 Int 方法返回值為 int64 類(lèi)型,SetInt 方法接收的參數(shù)類(lèi)型也是 int64 類(lèi)型。實(shí)際使用時(shí),可能需要轉(zhuǎn)化為實(shí)際的類(lèi)型:
package main
import (
"fmt"
"reflect"
)
func main() {
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 returns a uint64.
}運(yùn)行結(jié)果如下:
type: uint8
kind is uint8: true
其次,反射對(duì)象的 Kind 方法描述的是基礎(chǔ)類(lèi)型,而不是靜態(tài)類(lèi)型。如果一個(gè)反射對(duì)象包含了用戶(hù)定義類(lèi)型的值,如下所示:
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
上面的代碼中,雖然變量 v 的靜態(tài)類(lèi)型是 MyInt,而不是 int,但 Kind 方法仍然會(huì)返回 reflect.Int。換句話(huà)說(shuō) Kind 方法不會(huì)像 Type 方法一樣區(qū)分 MyInt 和 int。
反射第二定律:反射可以將“反射類(lèi)型對(duì)象”轉(zhuǎn)換為“接口類(lèi)型變量”
和物理學(xué)中的反射類(lèi)似,Go語(yǔ)言中的反射也能創(chuàng)造自己反面類(lèi)型的對(duì)象。
根據(jù)一個(gè) reflect.Value 類(lèi)型的變量,我們可以使用 Interface 方法恢復(fù)其接口類(lèi)型的值。事實(shí)上,這個(gè)方法會(huì)把 type 和 value 信息打包并填充到一個(gè)接口變量中,然后返回。
其函數(shù)聲明如下:
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
然后,我們可以通過(guò)斷言,恢復(fù)底層的具體值:
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
上面這段代碼會(huì)打印出一個(gè) float64 類(lèi)型的值,也就是反射類(lèi)型變量 v 所代表的值。
事實(shí)上,我們可以更好地利用這一特性,標(biāo)準(zhǔn)庫(kù)中的 fmt.Println 和 fmt.Printf 等函數(shù)都接收空接口變量作為參數(shù),fmt 包內(nèi)部會(huì)對(duì)接口變量進(jìn)行拆包,因此 fmt 包的打印函數(shù)在打印 reflect.Value 類(lèi)型變量的數(shù)據(jù)時(shí),只需要把 Interface 方法的結(jié)果傳給格式化打印程序:
fmt.Println(v.Interface())
為什么不直接使用 fmt.Println(v)?因?yàn)?v 的類(lèi)型是 reflect.Value,我們需要的是它的具體值,由于值的類(lèi)型是 float64,我們也可以用浮點(diǎn)格式化打印它:
fmt.Printf("value is %7.1e\n", v.Interface())
運(yùn)行結(jié)果如下:
3.4e+00
同樣,這次也不需要對(duì) v.Interface() 的結(jié)果進(jìn)行類(lèi)型斷言,空接口值內(nèi)部包含了具體值的類(lèi)型信息,Printf 函數(shù)會(huì)恢復(fù)類(lèi)型信息。
簡(jiǎn)單來(lái)說(shuō) Interface 方法和 ValueOf 函數(shù)作用恰好相反,唯一一點(diǎn)是,返回值的靜態(tài)類(lèi)型是 interface{}。
Go的反射機(jī)制可以將“接口類(lèi)型的變量”轉(zhuǎn)換為“反射類(lèi)型的對(duì)象”,然后再將“反射類(lèi)型對(duì)象”轉(zhuǎn)換過(guò)去。
反射第三定律:如果要修改“反射類(lèi)型對(duì)象”其值必須是“可寫(xiě)的”
這條定律很微妙,也很容易讓人迷惑,但是如果從第一條定律開(kāi)始看,應(yīng)該比較容易理解。
下面這段代碼雖然不能正常工作,但是非常值得研究:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic
如果運(yùn)行這段代碼,它會(huì)拋出一個(gè)奇怪的異常:
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
這里問(wèn)題不在于值7.1 不能被尋址,而是因?yàn)樽兞?v 是“不可寫(xiě)的”,“可寫(xiě)性”是反射類(lèi)型變量的一個(gè)屬性,但不是所有的反射類(lèi)型變量都擁有這個(gè)屬性。
我們可以通過(guò) CanSet 方法檢查一個(gè) reflect.Value 類(lèi)型變量的“可寫(xiě)性”,對(duì)于上面的例子,可以這樣寫(xiě):
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
} 運(yùn)行結(jié)果如下:
settability of v: false
對(duì)于一個(gè)不具有“可寫(xiě)性”的 Value 類(lèi)型變量,調(diào)用 Set 方法會(huì)報(bào)出錯(cuò)誤。
首先我們要弄清楚什么是“可寫(xiě)性”,“可寫(xiě)性”有些類(lèi)似于尋址能力,但是更嚴(yán)格,它是反射類(lèi)型變量的一種屬性,賦予該變量修改底層存儲(chǔ)數(shù)據(jù)的能力?!翱蓪?xiě)性”最終是由一個(gè)反射對(duì)象是否存儲(chǔ)了原始值而決定的。
示例代碼如下:
var x float64 = 3.4
v := reflect.ValueOf(x)
這里我們傳遞給 reflect.ValueOf 函數(shù)的是變量 x 的一個(gè)拷貝,而非 x 本身,想象一下如果下面這行代碼能夠成功執(zhí)行:
v.SetFloat(7.1)
如果這行代碼能夠成功執(zhí)行,它不會(huì)更新 x,雖然看起來(lái)變量 v 是根據(jù) x 創(chuàng)建的,相反它會(huì)更新 x 存在于反射對(duì)象 v 內(nèi)部的一個(gè)拷貝,而變量 x 本身完全不受影響。這會(huì)造成迷惑,并且沒(méi)有任何意義,所以是不合法的?!翱蓪?xiě)性”就是為了避免這個(gè)問(wèn)題而設(shè)計(jì)的。
這看起來(lái)很詭異,事實(shí)上并非如此,而且類(lèi)似的情況很常見(jiàn)。考慮下面這行代碼:
f(x)
代碼中,我們把變量 x 的一個(gè)拷貝傳遞給函數(shù),因此不期望它會(huì)改變 x 的值。如果期望函數(shù) f 能夠修改變量 x,我們必須傳遞 x 的地址(即指向 x 的指針)給函數(shù) f,如下所示:
f(&x)
反射的工作機(jī)制與此相同,如果想通過(guò)反射修改變量 x,就要把想要修改的變量的指針傳遞給反射庫(kù)。
首先,像通常一樣初始化變量 x,然后創(chuàng)建一個(gè)指向它的反射對(duì)象,命名為 p:
package main
import (
"fmt"
"reflect"
)
func main() {
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())
} 運(yùn)行結(jié)果如下:
type of p: *float64
settability of p: false
反射對(duì)象 p 是不可寫(xiě)的,但是我們也不像修改 p,事實(shí)上我們要修改的是 *p。為了得到 p 指向的數(shù)據(jù),可以調(diào)用 Value 類(lèi)型的 Elem 方法。Elem 方法能夠?qū)χ羔樳M(jìn)行“解引用”,然后將結(jié)果存儲(chǔ)到反射 Value 類(lèi)型對(duì)象 v 中:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
} 運(yùn)行結(jié)果如下:
settability of v: true
由于變量 v 代表 x, 因此我們可以使用 v.SetFloat 修改 x 的值:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
v := p.Elem()
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
}運(yùn)行結(jié)果如下:
7.1
7.1
反射不太容易理解,reflect.Type 和 reflect.Value 會(huì)混淆正在執(zhí)行的程序,但是它做的事情正是編程語(yǔ)言做的事情。只需要記?。褐灰瓷鋵?duì)象要修改它們表示的對(duì)象,就必須獲取它們表示的對(duì)象的地址。
結(jié)構(gòu)體
我們一般使用反射修改結(jié)構(gòu)體的字段,只要有結(jié)構(gòu)體的指針,我們就可以修改它的字段。
下面是一個(gè)解析結(jié)構(gòu)體變量 t 的例子,用結(jié)構(gòu)體的地址創(chuàng)建反射變量,再修改它。然后我們對(duì)它的類(lèi)型設(shè)置了 typeOfT,并用調(diào)用簡(jiǎn)單的方法迭代字段。
需要注意的是,我們從結(jié)構(gòu)體的類(lèi)型中提取了字段的名字,但每個(gè)字段本身是正常的 reflect.Value 對(duì)象。
package main
import (
"fmt"
"reflect"
)
func main() {
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())
}
}運(yùn)行結(jié)果如下:
0: A int = 23
1: B string = skidoo
T 字段名之所以大寫(xiě),是因?yàn)榻Y(jié)構(gòu)體中只有可導(dǎo)出的字段是“可設(shè)置”的。
因?yàn)?s 包含了一個(gè)可設(shè)置的反射對(duì)象,我們可以修改結(jié)構(gòu)體字段:
package main
import (
"fmt"
"reflect"
)
func main() {
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
}運(yùn)行結(jié)果如下:
t is now {77 Sunset Strip}
如果我們修改了程序讓 s 由 t(而不是 &t)創(chuàng)建,程序就會(huì)在調(diào)用 SetInt 和 SetString 的地方失敗,因?yàn)?t 的字段是不可設(shè)置的。
總結(jié)
反射規(guī)則可以總結(jié)為如下幾條:
- 反射可以將“接口類(lèi)型變量”轉(zhuǎn)換為“反射類(lèi)型對(duì)象”;
- 反射可以將“反射類(lèi)型對(duì)象”轉(zhuǎn)換為“接口類(lèi)型變量”;
- 如果要修改“反射類(lèi)型對(duì)象”,其值必須是“可寫(xiě)的”。
網(wǎng)站名稱(chēng):創(chuàng)新互聯(lián)GO教程:Go語(yǔ)言反射規(guī)則淺析
標(biāo)題來(lái)源:http://fisionsoft.com.cn/article/dhheosd.html


咨詢(xún)
建站咨詢(xún)
